Compare commits

...

26 Commits

Author SHA1 Message Date
libertyspy
8b3fe9b548 UPDATE: fk 2026-02-09 21:05:07 +08:00
libertyspy
db30244cd1 Initial commit 2026-02-09 21:03:57 +08:00
libertyspy
c9bc62fb8c Initial commit 2026-02-09 20:10:24 +08:00
libertyspy
9d54395c29 Initial commit 2026-02-09 20:05:56 +08:00
libertyspy
35fd1c8937 Initial commit 2026-02-09 19:53:17 +08:00
libertyspy
579d0f2bd8 Initial commit 2026-02-09 15:35:20 +08:00
libertyspy
7f8abf2ff2 Initial commit 2026-02-09 15:26:21 +08:00
libertyspy
f1bcd3812d Initial commit 2026-02-09 15:06:04 +08:00
libertyspy
d3fda27bb0 Initial commit 2026-02-09 15:04:34 +08:00
libertyspy
538a43f597 Merge remote-tracking branch 'origin/master' 2026-02-09 14:53:43 +08:00
libertyspy
fcab8585c5 UPDATE: fk 2026-02-09 14:53:05 +08:00
libertyspy
0ffc42ab69 UPDATE: fk 2026-02-09 14:52:56 +08:00
libertyspy
46bead08a0 UPDATE: fk 2026-02-09 14:52:46 +08:00
zouju
e07b11ef95 修改部分规则代码 2026-02-09 11:15:26 +08:00
libertyspy
a78781782e Initial commit 2026-02-08 22:32:50 +08:00
libertyspy
0b55384442 Initial commit 2026-02-08 22:31:13 +08:00
libertyspy
43837901f3 Initial commit 2026-02-08 22:30:08 +08:00
libertyspy
8898cd2e6f Initial commit 2026-02-08 22:28:52 +08:00
libertyspy
f34274ea35 Initial commit 2026-02-08 22:16:22 +08:00
libertyspy
5ffdb5e508 Initial commit 2026-02-08 22:09:28 +08:00
libertyspy
a5a3c93135 UPDATE: fk 2026-02-08 21:50:56 +08:00
libertyspy
af697e9304 UPDATE: fk 2026-02-08 21:50:33 +08:00
libertyspy
e7abfca9f7 Initial commit 2026-02-08 21:36:39 +08:00
libertyspy
1058d666a0 Initial commit 2026-02-08 20:57:07 +08:00
libertyspy
58d36a3d6d Initial commit 2026-02-08 20:41:49 +08:00
libertyspy
b544391b5c Initial commit 2026-02-08 20:27:40 +08:00
55 changed files with 2203 additions and 452 deletions

View File

@@ -1,8 +1,14 @@
package com.solution.web.controller.algo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -37,6 +43,51 @@ public class AlgorithmController extends BaseController {
@Autowired
private IAlgorithmService algorithmService;
/**
* 导出规则列表
*/
@ApiOperation("运行python程序")
@PostMapping("/run")
public Object run(@RequestBody Algorithm algorithm) {
List<Algorithm> list = algorithmService.selectAlgorithmList(algorithm);
ObjectMapper objectMapper = new ObjectMapper();
for (Algorithm al : list) {
String codePath = al.getCodePath();
try {
String jsonInput = objectMapper.writeValueAsString(al);
String pythonExe = "E:\\Apps\\anaconda3\\python.exe";
ProcessBuilder pb = new ProcessBuilder(pythonExe, codePath);
Process process = pb.start();
try (OutputStream os = process.getOutputStream()) {
os.write(jsonInput.getBytes(StandardCharsets.UTF_8));
os.flush();
}
String result;
// 修改你 Java 代码中的这一段
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
result = reader.lines().collect(Collectors.joining("\n"));
}
int exitCode = process.waitFor();
if (exitCode != 0) {
return "Python执行失败错误码" + exitCode;
}
return objectMapper.readTree(result);
} catch (Exception e) {
e.printStackTrace();
return "执行异常: " + e.getMessage();
}
}
return "未找到算法";
}
/**
* 查询规则列表
*/
@@ -56,7 +107,7 @@ public class AlgorithmController extends BaseController {
@PreAuthorize("@ss.hasPermi('algo:algorithm:export')")
@Log(title = "规则", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, Algorithm algorithm) {
public void export(HttpServletResponse response, @RequestBody Algorithm algorithm) {
List<Algorithm> list = algorithmService.selectAlgorithmList(algorithm);
ExcelUtil<Algorithm> util = new ExcelUtil<Algorithm>(Algorithm.class);
util.exportExcel(response, list, "规则数据");

View File

@@ -3,6 +3,7 @@ package com.solution.web.controller.behaviour;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.solution.web.core.BehaviortreeProcessor;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -38,6 +39,9 @@ public class BehaviortreeController extends BaseController
@Autowired
private IBehaviortreeService behaviortreeService;
@Autowired
private BehaviortreeProcessor behaviortreeProcessor;
/**
* 查询行为树主列表
*/
@@ -84,7 +88,7 @@ public class BehaviortreeController extends BaseController
@PostMapping
public AjaxResult add(@RequestBody Behaviortree behaviortree)
{
return toAjax(behaviortreeService.insertBehaviortree(behaviortree));
return toAjax(behaviortreeProcessor.create(behaviortree));
}
/**
@@ -96,7 +100,7 @@ public class BehaviortreeController extends BaseController
@PutMapping
public AjaxResult edit(@RequestBody Behaviortree behaviortree)
{
return toAjax(behaviortreeService.updateBehaviortree(behaviortree));
return toAjax(behaviortreeProcessor.update(behaviortree));
}
/**

View File

@@ -95,26 +95,26 @@ public class NodetemplateController extends BaseController {
if (CollectionUtils.isEmpty(list)) {
return R.ok(null);
}
// Map<String, List<NodetemplateDTO>> groupedByTemplateType = list.stream()
// .map(template -> {
// NodetemplateDTO dto = new NodetemplateDTO();
// dto.setId(template.getId());
// dto.setName(template.getName());
// dto.setDescription(template.getDescription());
// dto.setEnglishName(template.getEnglishName());
// dto.setLogicHandler(template.getLogicHandler());
// dto.setTempleteType(template.getTempleteType());
// return dto;
// })
// .collect(Collectors.groupingBy(NodetemplateDTO::getTempleteType));
// List<NodetemplateVO> vos = new ArrayList<>();
// groupedByTemplateType.forEach((key, value) -> {
// // 处理逻辑
// NodetemplateVO vo = new NodetemplateVO();
// vo.setTempleteType(key);
// vo.setDtoList(value);
// vos.add(vo);
// });
Map<String, List<NodetemplateDTO>> groupedByTemplateType = list.stream()
.map(template -> {
NodetemplateDTO dto = new NodetemplateDTO();
dto.setId(template.getId());
dto.setName(template.getName());
dto.setDescription(template.getDescription());
dto.setEnglishName(template.getEnglishName());
dto.setLogicHandler(template.getLogicHandler());
dto.setTemplateType(template.getTemplateType());
return dto;
})
.collect(Collectors.groupingBy(NodetemplateDTO::getTemplateType));
List<NodetemplateVO> vos = new ArrayList<>();
groupedByTemplateType.forEach((key, value) -> {
// 处理逻辑
NodetemplateVO vo = new NodetemplateVO();
vo.setTemplateType(key);
vo.setDtoList(value);
vos.add(vo);
});
return R.ok(list);
}

View File

@@ -23,7 +23,7 @@ public class NodetemplateDTO {
@ApiModelProperty("afsim 中转换的节点名")
private String englishName;
private String templeteType;
private String templateType;
public Long getId() {
return id;
@@ -65,11 +65,11 @@ public class NodetemplateDTO {
this.englishName = englishName;
}
public String getTempleteType() {
return templeteType;
public String getTemplateType() {
return templateType;
}
public void setTempleteType(String templeteType) {
this.templeteType = templeteType;
public void setTemplateType(String templateType) {
this.templateType = templateType;
}
}

View File

@@ -9,17 +9,17 @@ import java.util.List;
public class NodetemplateVO {
/** 模版类型节点模版或者条件判断例如“node”precondition“ */
@ApiModelProperty("模版类型节点模版或者条件判断例如“node”precondition“")
private String templeteType;
private String templateType;
@ApiModelProperty("节点模板数据")
private List<NodetemplateDTO> dtoList;
public String getTempleteType() {
return templeteType;
public String getTemplateType() {
return templateType;
}
public void setTempleteType(String templeteType) {
this.templeteType = templeteType;
public void setTemplateType(String templateType) {
this.templateType = templateType;
}
public List<NodetemplateDTO> getDtoList() {

View File

@@ -0,0 +1,160 @@
package com.solution.web.core;
/*
* 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.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.solution.system.domain.*;
import com.solution.system.service.IBehaviortreeService;
import com.solution.system.service.INodeconnectionService;
import com.solution.system.service.INodeparameterService;
import com.solution.system.service.ITreenodeinstanceService;
import com.solution.web.core.graph.Graph;
import com.solution.web.core.graph.GraphEdge;
import com.solution.web.core.graph.GraphNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class BehaviortreeProcessor {
@Autowired
private IBehaviortreeService behaviortreeService;
@Autowired
private ITreenodeinstanceService treenodeinstanceService;
@Autowired
private INodeparameterService nodeparameterService;
@Autowired
private INodeconnectionService nodeconnectionService;
private ObjectMapper objectMapper = createObjectMapper();
public static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
public int create(Behaviortree behaviortree) {
int result = behaviortreeService.insertBehaviortree(behaviortree);
processGraph(behaviortree);
return result;
}
public int update(Behaviortree behaviortree) {
int result = behaviortreeService.updateBehaviortree(behaviortree);
// 删除节点实例
treenodeinstanceService.deleteByTreeId(behaviortree.getId());
// 删除参数
nodeparameterService.deleteByTreeId(behaviortree.getId());
// 删除连线
nodeconnectionService.deleteByTreeId(behaviortree.getId());
processGraph(behaviortree);
return result;
}
private void processGraph(Behaviortree behaviortree) {
Graph graph = null;
try {
graph = objectMapper.readValue(behaviortree.getXmlContent(), Graph.class);
} catch (Exception e) {
// skip
e.printStackTrace();
}
if (null == graph) {
return;
}
// 插入节点 treenodeinstance
Map<String, Treenodeinstance> instanceKeyMap = new HashMap<>();
Map<String, Long> nodeKeyIndexMap = new HashMap<>();
if (graph.hasNodes()) {
Long index = 0L;
for (GraphNode node : graph.getNodes()) {
Treenodeinstance instance = createNodeInstance(behaviortree, node);
treenodeinstanceService.insertTreenodeinstance(instance);
instanceKeyMap.put(node.getKey(), instance);
if (node.hasParameters()) {
// 插入parameter nodeparameter
for (Templateparameterdef parameter : node.getParameters()) {
Nodeparameter nodeparameter = createNodeParameter(behaviortree, parameter, instance);
nodeparameterService.insertNodeparameter(nodeparameter);
}
}
nodeKeyIndexMap.put(node.getKey(), index);
index++;
}
}
// 插入连线 nodeconnection
if (graph.hasEdges()) {
for (GraphEdge edge : graph.getEdges()) {
Nodeconnection connection = createConnection(behaviortree, edge, instanceKeyMap, nodeKeyIndexMap);
nodeconnectionService.insertNodeconnection(connection);
}
}
}
private Nodeconnection createConnection(Behaviortree behaviortree, GraphEdge edge,
Map<String, Treenodeinstance> instanceKeyMap,
Map<String, Long> nodeKeyIndexMap) {
Nodeconnection connection = new Nodeconnection();
connection.setTreeId(behaviortree.getId());
if (null != instanceKeyMap.get(edge.getSource().getCell())) {
Treenodeinstance parent = instanceKeyMap.get(edge.getSource().getCell());
connection.setParentNodeId(parent.getId());
}
if (null != instanceKeyMap.get(edge.getTarget().getCell())) {
Treenodeinstance children = instanceKeyMap.get(edge.getTarget().getCell());
connection.setChildNodeId(children.getId());
}
if (null != nodeKeyIndexMap.get(edge.getSource().getCell())) {
connection.setOrderIndex(nodeKeyIndexMap.get(edge.getSource().getCell()));
}
return connection;
}
private Nodeparameter createNodeParameter(Behaviortree behaviortree, Templateparameterdef parameter,
Treenodeinstance instance) {
Nodeparameter nodeparameter = new Nodeparameter();
nodeparameter.setTreeId(behaviortree.getId());
nodeparameter.setNodeInstanceId(instance.getId());
nodeparameter.setParamDefId(parameter.getId());
nodeparameter.setValue(parameter.getDefaultValue());
return nodeparameter;
}
private Treenodeinstance createNodeInstance(Behaviortree behaviortree, GraphNode node) {
Treenodeinstance instance = new Treenodeinstance();
instance.setTreeId(behaviortree.getId());
instance.setTemplateId((long) node.getTemplate());
instance.setInstanceName(node.getName());
instance.setIsRoot((long) (node.isRoot() ? 1 : 0));
if ("precondition".equalsIgnoreCase(node.getTemplateType())) {
instance.setPreconditionTempleteId((long) node.getTemplate());
}
return instance;
}
}

View File

@@ -0,0 +1,52 @@
package com.solution.web.core.graph;
/*
* 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;
import java.util.List;
public class Graph implements Serializable {
private List<GraphNode> nodes;
private List<GraphEdge> edges;
public boolean hasNodes() {
return nodes != null && !nodes.isEmpty();
}
public boolean hasEdges() {
return edges != null && !edges.isEmpty();
}
@Override
public String toString() {
return "Graph{" +
"nodes=" + nodes +
", edges=" + edges +
'}';
}
public List<GraphNode> getNodes() {
return nodes;
}
public void setNodes(List<GraphNode> nodes) {
this.nodes = nodes;
}
public List<GraphEdge> getEdges() {
return edges;
}
public void setEdges(List<GraphEdge> edges) {
this.edges = edges;
}
}

View File

@@ -0,0 +1,85 @@
package com.solution.web.core.graph;
/*
* 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 org.springframework.util.StringUtils;
import java.io.Serializable;
public class GraphEdge implements Serializable {
private String key;
private Line source;
private Line target;
public boolean hasSource() {
return source != null && StringUtils.hasText(source.cell);
}
public boolean hasTarget() {
return target != null && StringUtils.hasText(target.cell);
}
public static class Line implements Serializable {
private String cell;
@Override
public String toString() {
return "Line{" +
"cell='" + cell + '\'' +
'}';
}
public String getCell() {
return cell;
}
public void setCell(String cell) {
this.cell = cell;
}
}
@Override
public String toString() {
return "GraphEdge{" +
"key='" + key + '\'' +
", source='" + source + '\'' +
", target='" + target + '\'' +
'}';
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Line getSource() {
return source;
}
public void setSource(Line source) {
this.source = source;
}
public Line getTarget() {
return target;
}
public void setTarget(Line target) {
this.target = target;
}
}

View File

@@ -0,0 +1,142 @@
package com.solution.web.core.graph;
/*
* 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.Templateparameterdef;
import java.io.Serializable;
import java.util.List;
public class GraphNode implements Serializable {
private int id;
private int template;
private String templateType;
private String type;
private String key;
private String name;
private String description;
private String category;
private List<Templateparameterdef> parameters;
private List<GraphVariable> variables;
public boolean hasParameters() {
return parameters != null && !parameters.isEmpty();
}
public boolean hasVariables() {
return variables != null && !variables.isEmpty();
}
public boolean isRoot() {
return "root".equalsIgnoreCase(this.category);
}
@Override
public String toString() {
return "GraphNode{" +
"id=" + id +
", key='" + key + '\'' +
", name='" + name + '\'' +
", description='" + description + '\'' +
", parameters=" + parameters +
", variables=" + variables +
'}';
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getTemplateType() {
return templateType;
}
public void setTemplateType(String templateType) {
this.templateType = templateType;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getTemplate() {
return template;
}
public void setTemplate(int template) {
this.template = template;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<Templateparameterdef> getParameters() {
return parameters;
}
public void setParameters(List<Templateparameterdef> parameters) {
this.parameters = parameters;
}
public List<GraphVariable> getVariables() {
return variables;
}
public void setVariables(List<GraphVariable> variables) {
this.variables = variables;
}
}

View File

@@ -0,0 +1,65 @@
package com.solution.web.core.graph;
/*
* 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 GraphVariable implements Serializable {
private String key;
private String value;
private String defaults;
private String unit;
@Override
public String toString() {
return "GraphVariable{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
", defaults='" + defaults + '\'' +
", unit='" + unit + '\'' +
'}';
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDefaults() {
return defaults;
}
public void setDefaults(String defaults) {
this.defaults = defaults;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
}

View File

@@ -0,0 +1,89 @@
package com.solution.algo;
/*
* 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.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.solution.algo.domain.AlgorithmConfig;
import org.apache.ibatis.type.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@Component
@MappedTypes(List.class)
@MappedJdbcTypes({
JdbcType.VARCHAR,
JdbcType.BLOB
})
public class AlgorithmConfigTypeHandler extends BaseTypeHandler<List<AlgorithmConfig>>
implements TypeHandler<List<AlgorithmConfig>> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ObjectMapper objectMapper = new ObjectMapper();
private final CollectionType collectionType = objectMapper.getTypeFactory()
.constructCollectionType(List.class, AlgorithmConfig.class);
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<AlgorithmConfig> parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, serialize(parameter));
}
public String serialize(List<AlgorithmConfig> indicatorConfig) {
if (null != indicatorConfig) {
try {
return objectMapper.writeValueAsString(indicatorConfig);
} catch (Exception e) {
logger.error("Can not serialize", e);
}
}
return null;
}
public List<AlgorithmConfig> deserialize(String config) throws SQLException {
if (StringUtils.hasText(config)) {
try {
return objectMapper.readValue(config, collectionType);
} catch (Exception e) {
logger.error("Can not deserialize", e);
}
}
return new ArrayList<>();
}
@Override
public List<AlgorithmConfig> getNullableResult(ResultSet rs, String columnName) throws SQLException {
String jsonValue = rs.getString(columnName);
return deserialize(jsonValue);
}
@Override
public List<AlgorithmConfig> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String jsonValue = rs.getString(columnIndex);
return deserialize(jsonValue);
}
@Override
public List<AlgorithmConfig> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String jsonValue = cs.getString(columnIndex);
return deserialize(jsonValue);
}
}

View File

@@ -34,6 +34,13 @@ public class Algorithm
@Excel(name = "算法描述")
private String description;
/** 算法配置 */
@Excel(name = "算法配置")
private String algoConfig;
/** 算法配置 */
private List<AlgorithmConfig> algoConfigList;
/** 算法参数定义信息 */
private List<AlgorithmParam> algorithmParamList;
@@ -87,6 +94,16 @@ public class Algorithm
return description;
}
public void setAlgoConfig(String algoConfig)
{
this.algoConfig = algoConfig;
}
public String getAlgoConfig()
{
return algoConfig;
}
public List<AlgorithmParam> getAlgorithmParamList()
{
return algorithmParamList;
@@ -97,6 +114,14 @@ public class Algorithm
this.algorithmParamList = algorithmParamList;
}
public List<AlgorithmConfig> getAlgoConfigList() {
return algoConfigList;
}
public void setAlgoConfigList(List<AlgorithmConfig> algoConfigList) {
this.algoConfigList = algoConfigList;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -105,6 +130,7 @@ public class Algorithm
.append("type", getType())
.append("codePath", getCodePath())
.append("description", getDescription())
.append("algoConfig", getAlgoConfig())
.append("algorithmParamList", getAlgorithmParamList())
.toString();
}

View File

@@ -0,0 +1,35 @@
package com.solution.algo.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 AlgorithmConfig implements Serializable {
private String name;
private String operation;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
}

View File

@@ -10,6 +10,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="type" column="type" />
<result property="codePath" column="code_path" />
<result property="description" column="description" />
<result property="algoConfig" column="algo_config" />
<result property="algoConfigList" column="algo_config_list" typeHandler="com.solution.algo.AlgorithmConfigTypeHandler" />
</resultMap>
<resultMap id="AlgorithmAlgorithmParamResult" type="Algorithm" extends="AlgorithmResult">
@@ -25,7 +27,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectAlgorithmVo">
select id, name, type, code_path, description from algorithm
select id, name, type, code_path, description, algo_config, algo_config_list from algorithm
</sql>
<select id="selectAlgorithmList" parameterType="Algorithm" resultMap="AlgorithmAlgorithmParamResult">
@@ -35,11 +37,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="type != null and type != ''"> and type = #{type}</if>
<if test="codePath != null and codePath != ''"> and code_path = #{codePath}</if>
<if test="description != null and description != ''"> and description = #{description}</if>
<if test="algoConfig != null and algoConfig != ''"> and algo_config = #{algoConfig}</if>
</where>
</select>
<select id="selectAlgorithmById" parameterType="Long" resultMap="AlgorithmAlgorithmParamResult">
select id, name, type, code_path, description
select id, name, type, code_path, description, algo_config
from algorithm
where id = #{id}
</select>
@@ -57,12 +60,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="type != null and type != ''">type,</if>
<if test="codePath != null">code_path,</if>
<if test="description != null">description,</if>
<if test="algoConfig != null">algo_config,</if>
<if test="algoConfigList != null">algo_config_list,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">#{name},</if>
<if test="type != null and type != ''">#{type},</if>
<if test="codePath != null">#{codePath},</if>
<if test="description != null">#{description},</if>
<if test="algoConfig != null">#{algoConfig},</if>
<if test="algoConfigList != null">#{algoConfigList,typeHandler=com.solution.algo.AlgorithmConfigTypeHandler},</if>
</trim>
</insert>
@@ -73,6 +80,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="type != null and type != ''">type = #{type},</if>
<if test="codePath != null">code_path = #{codePath},</if>
<if test="description != null">description = #{description},</if>
<if test="algoConfig != null">algo_config = #{algoConfig},</if>
<if test="algoConfigList != null">algo_config_list = #{algoConfigList,typeHandler=com.solution.algo.AlgorithmConfigTypeHandler},</if>
</trim>
where id = #{id}
</update>

View File

@@ -18,6 +18,8 @@ public class Nodeconnection extends BaseEntity
/** 连接ID (主键) */
private Long id;
private Long treeId;
/** 父节点 (外键: TreeInstanceNode.id) */
@Excel(name = "父节点 (外键: TreeInstanceNode.id)")
private Long parentNodeId;
@@ -40,7 +42,15 @@ public class Nodeconnection extends BaseEntity
return id;
}
public void setParentNodeId(Long parentNodeId)
public Long getTreeId() {
return treeId;
}
public void setTreeId(Long treeId) {
this.treeId = treeId;
}
public void setParentNodeId(Long parentNodeId)
{
this.parentNodeId = parentNodeId;
}

View File

@@ -18,6 +18,8 @@ public class Nodeparameter extends BaseEntity
/** 节点参数ID (主键) */
private Long id;
private Long treeId;
/** 关联到哪个节点实例 (外键: TreeInstanceNode.id) */
@Excel(name = "关联到哪个节点实例 (外键: TreeInstanceNode.id)")
private Long nodeInstanceId;
@@ -40,7 +42,15 @@ public class Nodeparameter extends BaseEntity
return id;
}
public void setNodeInstanceId(Long nodeInstanceId)
public Long getTreeId() {
return treeId;
}
public void setTreeId(Long treeId) {
this.treeId = treeId;
}
public void setNodeInstanceId(Long nodeInstanceId)
{
this.nodeInstanceId = nodeInstanceId;
}

View File

@@ -40,7 +40,7 @@ public class Nodetemplate extends BaseEntity
/** 模版类型节点模版或者条件判断例如“node”precondition“ */
@Excel(name = "模版类型节点模版或者条件判断例如“node”precondition“")
private String templeteType;
private String templateType;
public Nodetemplate() {
}
@@ -52,7 +52,7 @@ public class Nodetemplate extends BaseEntity
this.logicHandler = template.logicHandler;
this.description = template.description;
this.englishName = template.englishName;
this.templeteType = template.templeteType;
this.templateType = template.templateType;
}
public void setId(Long id)
@@ -115,14 +115,14 @@ public class Nodetemplate extends BaseEntity
return englishName;
}
public void setTempleteType(String templeteType)
public void setTemplateType(String templateType)
{
this.templeteType = templeteType;
this.templateType = templateType;
}
public String getTempleteType()
public String getTemplateType()
{
return templeteType;
return templateType;
}
@Override
@@ -134,7 +134,7 @@ public class Nodetemplate extends BaseEntity
.append("logicHandler", getLogicHandler())
.append("description", getDescription())
.append("englishName", getEnglishName())
.append("templeteType", getTempleteType())
.append("templateType", getTemplateType())
.toString();
}
}

View File

@@ -2,6 +2,7 @@ package com.solution.system.mapper;
import java.util.List;
import com.solution.system.domain.Nodeconnection;
import org.apache.ibatis.annotations.Param;
/**
* 节点连接Mapper接口
@@ -11,6 +12,8 @@ import com.solution.system.domain.Nodeconnection;
*/
public interface NodeconnectionMapper
{
void deleteByTreeId(@Param("treeId") Long treeId);
/**
* 查询节点连接
*

View File

@@ -2,6 +2,7 @@ package com.solution.system.mapper;
import java.util.List;
import com.solution.system.domain.Nodeparameter;
import org.apache.ibatis.annotations.Param;
/**
* 节点参数Mapper接口
@@ -11,6 +12,8 @@ import com.solution.system.domain.Nodeparameter;
*/
public interface NodeparameterMapper
{
void deleteByTreeId(@Param("treeId") Long treeId);
/**
* 查询节点参数
*

View File

@@ -2,6 +2,7 @@ package com.solution.system.mapper;
import java.util.List;
import com.solution.system.domain.Treenodeinstance;
import org.apache.ibatis.annotations.Param;
/**
* 行为树实例节点Mapper接口
@@ -11,6 +12,8 @@ import com.solution.system.domain.Treenodeinstance;
*/
public interface TreenodeinstanceMapper
{
void deleteByTreeId(@Param(value = "treeId") Long treeId);
/**
* 查询行为树实例节点
*

View File

@@ -2,6 +2,7 @@ package com.solution.system.service;
import java.util.List;
import com.solution.system.domain.Nodeconnection;
import org.apache.ibatis.annotations.Param;
/**
* 节点连接Service接口
@@ -11,6 +12,9 @@ import com.solution.system.domain.Nodeconnection;
*/
public interface INodeconnectionService
{
void deleteByTreeId(@Param("treeId") Long treeId);
/**
* 查询节点连接
*

View File

@@ -11,6 +11,9 @@ import com.solution.system.domain.Nodeparameter;
*/
public interface INodeparameterService
{
void deleteByTreeId(Long treeId);
/**
* 查询节点参数
*

View File

@@ -11,6 +11,9 @@ import com.solution.system.domain.Treenodeinstance;
*/
public interface ITreenodeinstanceService
{
void deleteByTreeId(Long treeId);
/**
* 查询行为树实例节点
*

View File

@@ -19,6 +19,11 @@ public class NodeconnectionServiceImpl implements INodeconnectionService
@Autowired
private NodeconnectionMapper nodeconnectionMapper;
@Override
public void deleteByTreeId(Long treeId) {
nodeconnectionMapper.deleteByTreeId(treeId);
}
/**
* 查询节点连接
*

View File

@@ -19,6 +19,11 @@ public class NodeparameterServiceImpl implements INodeparameterService
@Autowired
private NodeparameterMapper nodeparameterMapper;
@Override
public void deleteByTreeId(Long treeId) {
nodeparameterMapper.deleteByTreeId(treeId);
}
/**
* 查询节点参数
*

View File

@@ -19,6 +19,11 @@ public class TreenodeinstanceServiceImpl implements ITreenodeinstanceService
@Autowired
private TreenodeinstanceMapper treenodeinstanceMapper;
@Override
public void deleteByTreeId(Long treeId) {
treenodeinstanceMapper.deleteByTreeId(treeId);
}
/**
* 查询行为树实例节点
*

View File

@@ -6,18 +6,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="Nodeconnection" id="NodeconnectionResult">
<result property="id" column="id" />
<result property="treeId" column="tree_id" />
<result property="parentNodeId" column="parent_node_id" />
<result property="childNodeId" column="child_node_id" />
<result property="orderIndex" column="order_index" />
</resultMap>
<delete id="deleteByTreeId">
delete from nodeconnection where tree_id=#{treeId}
</delete>
<sql id="selectNodeconnectionVo">
select id, parent_node_id, child_node_id, order_index from nodeconnection
select id, tree_id, parent_node_id, child_node_id, order_index from nodeconnection
</sql>
<select id="selectNodeconnectionList" parameterType="Nodeconnection" resultMap="NodeconnectionResult">
<include refid="selectNodeconnectionVo"/>
<where>
<where>
<if test="treeId != null "> and tree_id = #{treeId}</if>
<if test="parentNodeId != null "> and parent_node_id = #{parentNodeId}</if>
<if test="childNodeId != null "> and child_node_id = #{childNodeId}</if>
<if test="orderIndex != null "> and order_index = #{orderIndex}</if>
@@ -32,11 +38,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertNodeconnection" parameterType="Nodeconnection" useGeneratedKeys="true" keyProperty="id">
insert into nodeconnection
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="treeId != null">tree_id,</if>
<if test="parentNodeId != null">parent_node_id,</if>
<if test="childNodeId != null">child_node_id,</if>
<if test="orderIndex != null">order_index,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="treeId != null">#{treeId},</if>
<if test="parentNodeId != null">#{parentNodeId},</if>
<if test="childNodeId != null">#{childNodeId},</if>
<if test="orderIndex != null">#{orderIndex},</if>
@@ -46,6 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateNodeconnection" parameterType="Nodeconnection">
update nodeconnection
<trim prefix="SET" suffixOverrides=",">
<if test="treeId != null">tree_id = #{treeId},</if>
<if test="parentNodeId != null">parent_node_id = #{parentNodeId},</if>
<if test="childNodeId != null">child_node_id = #{childNodeId},</if>
<if test="orderIndex != null">order_index = #{orderIndex},</if>

View File

@@ -6,13 +6,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="Nodeparameter" id="NodeparameterResult">
<result property="id" column="id" />
<result property="treeId" column="tree_id" />
<result property="nodeInstanceId" column="node_instance_id" />
<result property="paramDefId" column="param_def_id" />
<result property="value" column="value" />
</resultMap>
<delete id="deleteByTreeId">
delete from nodeparameter where tree_id=#{treeId}
</delete>
<sql id="selectNodeparameterVo">
select id, node_instance_id, param_def_id, value from nodeparameter
select id, treeId, node_instance_id, param_def_id, value from nodeparameter
</sql>
<select id="selectNodeparameterList" parameterType="Nodeparameter" resultMap="NodeparameterResult">
@@ -32,11 +37,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertNodeparameter" parameterType="Nodeparameter" useGeneratedKeys="true" keyProperty="id">
insert into nodeparameter
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="treeId != null">tree_id,</if>
<if test="nodeInstanceId != null">node_instance_id,</if>
<if test="paramDefId != null">param_def_id,</if>
<if test="value != null">value,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="treeId != null">#{treeId},</if>
<if test="nodeInstanceId != null">#{nodeInstanceId},</if>
<if test="paramDefId != null">#{paramDefId},</if>
<if test="value != null">#{value},</if>
@@ -46,6 +53,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateNodeparameter" parameterType="Nodeparameter">
update nodeparameter
<trim prefix="SET" suffixOverrides=",">
<if test="treeId != null">tree_id = #{treeId},</if>
<if test="nodeInstanceId != null">node_instance_id = #{nodeInstanceId},</if>
<if test="paramDefId != null">param_def_id = #{paramDefId},</if>
<if test="value != null">value = #{value},</if>

View File

@@ -11,7 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="logicHandler" column="logic_handler" />
<result property="description" column="description" />
<result property="englishName" column="english_name" />
<result property="templeteType" column="templete_type" />
<result property="templateType" column="templete_type" />
</resultMap>
<sql id="selectNodetemplateVo">
@@ -26,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="logicHandler != null and logicHandler != ''"> and logic_handler = #{logicHandler}</if>
<if test="description != null and description != ''"> and description = #{description}</if>
<if test="englishName != null and englishName != ''"> and english_name like concat('%', #{englishName}, '%')</if>
<if test="templeteType != null and templeteType != ''"> and templete_type = #{templeteType}</if>
<if test="templateType != null and templateType != ''"> and templete_type = #{templateType}</if>
</where>
</select>
@@ -43,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="logicHandler != null">logic_handler,</if>
<if test="description != null">description,</if>
<if test="englishName != null">english_name,</if>
<if test="templeteType != null and templeteType != ''">templete_type,</if>
<if test="templateType != null and templateType != ''">templete_type,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="type != null and type != ''">#{type},</if>
@@ -51,7 +51,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="logicHandler != null">#{logicHandler},</if>
<if test="description != null">#{description},</if>
<if test="englishName != null">#{englishName},</if>
<if test="templeteType != null and templeteType != ''">#{templeteType},</if>
<if test="templateType != null and templateType != ''">#{templateType},</if>
</trim>
</insert>
@@ -63,7 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="logicHandler != null">logic_handler = #{logicHandler},</if>
<if test="description != null">description = #{description},</if>
<if test="englishName != null">english_name = #{englishName},</if>
<if test="templeteType != null and templeteType != ''">templete_type = #{templeteType},</if>
<if test="templateType != null and templateType != ''">templete_type = #{templateType},</if>
</trim>
where id = #{id}
</update>

View File

@@ -15,6 +15,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="desciption" column="desciption" />
</resultMap>
<delete id="deleteByTreeId">
delete from treenodeinstance where tree_id=#{treeId}
</delete>
<sql id="selectTreenodeinstanceVo">
select id, tree_id, template_id, instance_name, is_root, precondition_templete_id, uuid,desciption from treenodeinstance
</sql>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -34,4 +34,12 @@ export const routes: RouteRecordRaw[] = [
},
component: () => import('@/views/decision/designer.vue'),
},
{
name: 'decision-algorithm-management',
path: '/app/decision/algorithm/management',
meta: {
title: '指挥决策规则库管理',
},
component: () => import('@/views/decision/algorithm/management.vue'),
},
]

View File

@@ -270,20 +270,68 @@
border-radius: 0;
min-height: calc(100vh - 90px);
background: transparent;
background: url('@/assets/icons/bg-fk.png') center / 100% 100%;
.ant-tabs .ant-tabs-ink-bar {
position: absolute;
background: transparent;
pointer-events: none;
}
.ant-steps-horizontal{
&.ant-steps.ant-steps-navigation .ant-steps-item.ant-steps-item-active::before {
width: 30%;
margin-left: 37%;
display: none;
}
.ant-steps-item-active{
.ant-steps-item-container{
&:after{
height: 1px;
content: '';
position: absolute;
bottom: 0;
background: #377ead;
display: block;
width: 100px;
}
}
}
}
.ant-card-head {
border: 0;
color: #11aed7;
color: #a2b1ba;
}
& > .ant-card-head {
height: 50px;
height: 40px;
min-height: 40px;
font-size: 16px;
border-radius: 0;
padding: 5px 10px;
}
& > .ant-card-head {
background: url('@/assets/icons/page-card-head.png') center / 100% 100%;
background: url('@/assets/icons/bg-fk-title.png') center / 100% 100%;
color: #eee;
.point {
background: url('@/assets/icons/bg-fk-point.png') center / 100% 100%;
width: 25px;
height: 25px;
display: block;
margin-top: 5px;
}
.text {
margin-top: 5px;
display: block;
font-size: 16px;
}
}
& > .ant-card-body {
@@ -979,6 +1027,8 @@
border-right: 1px solid #062850;
width: 100%;
overflow: hidden;
background: #081229;
}
}
@@ -986,6 +1036,7 @@
width: 300px;
float: left;
margin-left: -1px;
background: #041b36db;
.observation-items{
//height: 160px;
@@ -1319,11 +1370,6 @@
}
}
.ant-btn-default {
background: #7dd3f9;
border-color: #7dd3f9;
color: #000;
}
.btn-field {
min-width: 120px;
@@ -1484,4 +1530,115 @@
.ant-divider {
border-block-start: 1px solid rgb(255 255 255 / 17%);
}
.ant-pagination {
position: absolute;
bottom: 10px;
width: 100%;
.ant-pagination-disabled .ant-pagination-item-link,
.ant-pagination-disabled:hover .ant-pagination-item-link,
.ant-pagination-prev .ant-pagination-item-link,
.ant-pagination-next .ant-pagination-item-link,
&.ant-pagination-mini .ant-pagination-total-text,
&.ant-pagination-mini .ant-pagination-simple-pager {
color: rgb(255 255 255 / 95%);
}
&.ant-pagination-simple .ant-pagination-simple-pager input {
background-color: transparent;
border: 1px solid #0f4a7c;
color: #eee;
}
}
.ant-empty-small {
margin-block: 8px;
color: rgb(207 207 207 / 66%);
}
.ant-btn >span {
display: inline-block;
float: inherit;
}
.ant-btn,
.ant-input {
border-radius: 2px;
}
.ant-btn-primary {
color: #eee;
//border-color: #1b5b7d;
//background: linear-gradient(90deg, #1e4c65 0%, #0b3a62 100%);
border-color: #29759c;
background: linear-gradient(0deg, #0f374c 0, #154c69 100%);
&.selected,
&:not(:disabled):hover,
&:hover,
&:active {
border-color: #166094;
background: linear-gradient(90deg, #3687bc 0%, #074375 100%);
color: #fff;
}
}
.ant-btn-default {
&.ant-btn-dangerous{
background: transparent!important;
}
color: #eee;
//border-color: #144087;
//background: linear-gradient(90deg, #0f4a7c 0%, #0a365b 100%);
border-color: #1c468b;
background: linear-gradient(0deg, #14223b 0, #1c468b 100%);
&.selected,
&:not(:disabled):hover,
&:hover,
&:active {
color: #fff;
border-color: #166094;
background: linear-gradient(90deg, #3687bc 0%, #074375 100%);
}
&:disabled,
&.disabled{
color: #a9a3a3;
&:hover{
color:#eee;
}
}
}
.btn-field {
min-width: 120px;
background: #5796b2;
text-align: center;
cursor: pointer;
border-radius: 2px;
color: #fff;
padding: 5px;
border: 1px solid #5796b2;
.anticon {
margin-left: 10px;
}
&:hover,
&.selected {
background: #19b0ff;
border-color: #19b0ff;
}
&.gold {
background: transparent;
border-color: #b5b39d;
color: #b5b39d;
cursor: pointer;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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 { Algorithm, AlgorithmPageableResponse, AlgorithmRequest } from './types';
import type { BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api',
});
export const findAlgorithmsByQuery = (query: Partial<AlgorithmRequest> = {}): Promise<AlgorithmPageableResponse> => {
return req.get('/algo/algorithm/list', query);
};
export const createAlgorithm = (algorithm: Algorithm): Promise<BasicResponse> => {
return req.postJson('/algo/algorithm', algorithm);
};
export const runAlgorithm = (algorithm: Algorithm): Promise<BasicResponse> => {
return req.postJson('/algo/algorithm/run', algorithm);
};
export const updateAlgorithm = (algorithm: Algorithm): Promise<BasicResponse> => {
return req.putJson('/algo/algorithm', algorithm);
};
export const deleteAlgorithm = (id: number): Promise<BasicResponse> => {
return req.delete(`/algo/algorithm/${id}`);
};

View File

@@ -0,0 +1,42 @@
/*
* 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.
*/
export const algorithmTypes = [
{
type: 'defense',
name: '防守',
},
{
type: 'offense',
name: '进攻',
},
{
type: 'formation',
name: '队形',
}
]
export const algorithmConfigOperations = [
{
type: '+',
name: '加',
},
{
type: '-',
name: '减',
},
{
type: '*',
name: '乘',
},
{
type: '/',
name: '除',
},
]

View File

@@ -0,0 +1,513 @@
<template>
<Layout>
<template #sidebar>
<div class="ks-sidebar-header">
<a-flex class="ks-sidebar-title">
<span class="icon"></span>
<span class="text">指挥决策规则库管理</span>
</a-flex>
<a-button class="ks-sidebar-add" size="small" @click="handleCreate">
<PlusOutlined />
新增
</a-button>
</div>
<a-list item-layout="horizontal" :data-source="algorithms" class="ks-algorithm-list">
<template #renderItem="{ item }">
<a-list-item @click="()=> handleSelect(item)" :class="selectedAlgorithm?.id === item.id ? 'selected' : null">
<a-list-item-meta :description="substring(item.description,20)">
<template #title>
<span class="ks-algorithm-name">{{ substring(item.name, 20) }}</span>
<span class="ks-algorithm-type"><a-badge size="small" :count="getAlgorithmTypeName(item.type)"></a-badge></span>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<a-pagination
v-model:current="query.pageNum"
:page-size="query.pageSize"
:total="algorithmsTotal"
simple size="small" @change="handleChange" />
</template>
<div class="w-full h-full">
<a-card class="ks-page-card ks-algorithm-card">
<template #title>
<a-space>
<span class="point"></span>
<span class="text">规则配置</span>
</a-space>
</template>
<div class="ks-scrollable" style="height: 80.5vh;overflow-y: auto;padding-right: 10px">
<a-row :gutter="15">
<a-col :span="16">
<a-form
ref="formRef"
:label-col="{span: 6}"
:model="selectedAlgorithm"
autocomplete="off"
layout="horizontal"
name="basic"
>
<a-form-item
label="规则名称"
:rules="[{ required: true, message: '请输入算法名称!', trigger: ['input', 'change'] }]"
:name="['name']"
>
<a-input v-model:value="selectedAlgorithm.name" placeholder="请输入算法名称" />
</a-form-item>
<a-form-item
label="业务类型"
:rules="[{ required: true, message: '请输入业务类型!', trigger: ['input', 'change'] }]"
:name="['type']"
>
<a-select placeholder="请选择业务类型" allow-clear v-model:value="selectedAlgorithm.type" @change="(v: any)=> selectedAlgorithm.type = v">
<a-select-option v-for="a in algorithmTypes" :value="a.type">{{ a.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="算法文件路径"
:rules="[{ required: true, message: '请输入算法文件路径!', trigger: ['input', 'change'] }]"
:name="['codePath']"
>
<a-input v-model:value="selectedAlgorithm.codePath" placeholder="请输入算法文件路径" />
</a-form-item>
<a-form-item
label="算法描述"
:rules="[{ required: false, message: '请输入算法描述', trigger: ['input', 'change'] }]"
:name="['description']"
>
<a-textarea v-model:value="selectedAlgorithm.description" placeholder="请输入算法描述" />
</a-form-item>
<a-form-item
label="算法参数定义"
:rules="[{ required: true, message: '请输入算法参数定义', trigger: ['input', 'change'] }]"
:name="['algorithmParamList']"
>
<a-form-item-rest>
<div class="ks-algorithm-param-list">
<div class="ks-algorithm-param-item" v-for="(item,index) in selectedAlgorithm.algorithmParamList">
<a-row :gutter="15">
<a-col :span="7">
<a-input v-model:value="item.paramName" placeholder="请输入参数名" />
</a-col>
<a-col :span="7">
<a-input v-model:value="item.defaultValue" placeholder="请输入默认值" />
</a-col>
<a-col :span="7">
<a-input v-model:value="item.description" placeholder="请输入描述" />
</a-col>
<a-col :span="3">
<a-space class="ks-algorithm-param-actions">
<MinusCircleOutlined @click="()=> handleMinus(index)" />
<PlusCircleOutlined @click="handleAdd" v-if="index === 0" />
</a-space>
</a-col>
</a-row>
</div>
</div>
</a-form-item-rest>
</a-form-item>
<a-form-item
label="算法配置"
:rules="[{ required: false, message: '请输入算法配置', trigger: ['input', 'change'] }]"
:name="['algoConfig']"
>
<a-form-item-rest>
<div class="ks-algorithm-param-list">
<div class="ks-algorithm-param-item" v-for="(t,index) in selectedAlgorithm.algoConfigList">
<a-row :gutter="15">
<a-col :span="7">
<a-select placeholder="请选择参数" v-model:value="t.name" @change="(v: any)=> t.name = v">
<a-select-option :value="v.paramName" v-for="v in selectedAlgorithm.algorithmParamList">{{v.paramName}}</a-select-option>
</a-select>
</a-col>
<a-col :span="7">
<a-select placeholder="请选择操作" v-model:value="t.operation" @change="(v: any)=> t.operation = v">
<a-select-option :value="o.type" v-for="o in algorithmConfigOperations">{{o.name}} {{o.type}}</a-select-option>
</a-select>
</a-col>
<a-col :span="3">
<a-space class="ks-algorithm-param-actions">
<MinusCircleOutlined @click="()=> handleMinusConfig(index)" />
<PlusCircleOutlined @click="handleAddConfig" v-if="index === 0" />
</a-space>
</a-col>
</a-row>
</div>
</div>
</a-form-item-rest>
</a-form-item>
<a-form-item
:wrapper-col="{offset: 6}"
>
<a-space>
<a-button @click="handleSave" type="primary">保存规则</a-button>
<a-popconfirm
v-if="selectedAlgorithm && selectedAlgorithm.id > 0"
title="确定运行?"
@confirm="handleRun"
>
<a-button>运行规则</a-button>
</a-popconfirm>
<a-popconfirm
v-if="selectedAlgorithm && selectedAlgorithm.id > 0"
title="确定删除?"
@confirm="handleDelete"
>
<a-button danger>删除规则</a-button>
</a-popconfirm>
</a-space>
</a-form-item>
</a-form>
</a-col>
</a-row>
</div>
</a-card>
</div>
</Layout>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { type FormInstance, message } from 'ant-design-vue';
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
import Layout from '../layout.vue';
import { createAlgorithm, deleteAlgorithm, findAlgorithmsByQuery, updateAlgorithm, runAlgorithm } from './api';
import type { Algorithm, AlgorithmParam, AlgorithmRequest } from './types';
import { substring } from '@/utils/strings';
import { algorithmTypes,algorithmConfigOperations } from './config';
import type { NullableString } from '@/types';
const query = ref<Partial<AlgorithmRequest>>({
pageNum: 1,
pageSize: 10,
});
const defaultAlgorithmParam: AlgorithmParam = {
id: 0,
algorithmId: 0,
// 参数名
paramName: null,
// 默认值
defaultValue: null,
// 描述
description: null,
};
const defaultAlgorithm: Algorithm = {
id: 0,
// 算法名称
name: null,
// 业务类型
type: undefined,
// 算法文件路径
codePath: null,
// 算法描述
description: null,
// 算法配置
algoConfig: null,
// 算法配置
algoConfigList: [],
// 算法参数定义信息
algorithmParamList: [
{ ...defaultAlgorithmParam },
],
};
const resolveItem = (item: Algorithm) => {
let newItem = JSON.parse(JSON.stringify(item))
if (!newItem.algorithmParamList) {
newItem.algorithmParamList = [];
}
if (newItem.algorithmParamList.length === 0) {
newItem.algorithmParamList.push({ ...defaultAlgorithmParam });
}
try{
newItem.algoConfigList = JSON.parse(newItem.algoConfigList)
} catch (e: any){
console.error(e);
}
if(!newItem.algoConfigList){
newItem.algoConfigList = []
}
if(newItem.algoConfigList.length === 0){
newItem.algoConfigList.push({
name: undefined,
operation: undefined,
})
}
return newItem;
};
const algorithms = ref<Algorithm[]>([]);
const algorithmsTotal = ref<number>(0);
const selectedAlgorithm = ref<Algorithm>(resolveItem(defaultAlgorithm));
const formRef = ref<FormInstance | null>(null);
const getAlgorithmTypeName = (type: NullableString): NullableString => {
let v = algorithmTypes.find(c => c.type === type);
return v && v.name ? v.name : type;
};
const load = () => {
algorithms.value = [];
algorithmsTotal.value = 0;
formRef.value?.resetFields();
selectedAlgorithm.value = resolveItem(defaultAlgorithm);
findAlgorithmsByQuery(query.value).then(r => {
algorithms.value = r.rows ?? [];
algorithmsTotal.value = r.total ?? 0;
});
};
const handleCreate = () => {
selectedAlgorithm.value = resolveItem(defaultAlgorithm);
};
const handleSelect = (item: Algorithm) => {
selectedAlgorithm.value = resolveItem(item);
};
const handleAdd = () => {
selectedAlgorithm.value.algorithmParamList.push({ ...defaultAlgorithmParam });
};
const handleMinus = (index: number) => {
const paramList = selectedAlgorithm.value.algorithmParamList;
if (index === 0 && selectedAlgorithm.value.algorithmParamList.length === 1) {
selectedAlgorithm.value.algorithmParamList = [{ ...defaultAlgorithmParam }];
} else if (index >= 0 && index < paramList.length && paramList.length > 1) {
paramList.splice(index, 1);
selectedAlgorithm.value.algorithmParamList = [...paramList];
}
};
const handleAddConfig = () => {
selectedAlgorithm.value.algoConfigList.push({ name: null, operation: null });
};
const handleMinusConfig = (index: number) => {
const paramList = selectedAlgorithm.value.algoConfigList;
if (index === 0 && selectedAlgorithm.value.algoConfigList.length === 1) {
selectedAlgorithm.value.algoConfigList = [{ name: null, operation: null }];
} else if (index >= 0 && index < paramList.length && paramList.length > 1) {
paramList.splice(index, 1);
selectedAlgorithm.value.algoConfigList = [...paramList];
}
};
const handleDelete = () => {
if (selectedAlgorithm.value && selectedAlgorithm.value.id > 0) {
deleteAlgorithm(selectedAlgorithm.value.id).then(r => {
if (r.code === 200) {
load();
message.info(r.msg ?? '删除成功');
} else {
message.error(r.msg ?? '删除失败');
}
});
}
};
const handleSave = () => {
if (formRef.value) {
formRef.value.validate().then(() => {
let res = null;
let savedValue: Algorithm = JSON.parse(JSON.stringify(selectedAlgorithm.value)) as any as Algorithm;
let algoConfig = '';
if( savedValue.algoConfigList ){
savedValue.algoConfigList.forEach(o=> {
if(o.name){
algoConfig = algoConfig + o.name;
}
if(o.operation){
algoConfig = algoConfig + o.operation;
}
})
}
savedValue.algoConfig = algoConfig;
if (savedValue.id > 0) {
res = updateAlgorithm(savedValue);
} else {
res = createAlgorithm(savedValue);
}
if (res) {
res.then(r => {
if (r.code === 200) {
load();
message.info(r.msg ?? '操作成功');
} else {
message.error(r.msg ?? '操作失败');
}
});
}
});
}
};
const handleRun = ()=> {
if (formRef.value) {
formRef.value.validate().then(() => {
runAlgorithm(selectedAlgorithm.value).then(r => {
if (r.code === 200) {
message.info(r.msg ?? '运行成功');
} else {
message.error(r.msg ?? '运行失败');
}
});
});
}
}
const handleChange = (page: number, pageSize: number) => {
query.value.pageNum = page;
query.value.pageSize = pageSize;
load();
};
onMounted(() => load());
</script>
<style lang="less">
.ks-algorithm-card {
.ant-card-head-title {
span.text {
display: block;
line-height: 30px;
}
}
}
.ks-sidebar-header {
line-height: 40px;
padding: 5px 15px;
background: #081d36;
min-height: 40px;
background: url(@/assets/icons/card-head.png) left / 180% 100%;
padding: 0 10px;
.ks-sidebar-title {
color: #7ae8fc;
font-size: 16px;
.icon {
background: url(@/assets/icons/list.png) center / 100% 100%;
width: 25px;
height: 25px;
display: block;
margin-top: 7px;
}
.text{
margin-left: 40px;
font-size: 16px;
color: #eee;
}
}
.ks-sidebar-add {
position: absolute;
right: 7px;
top: 8px;
font-size: 12px;
.anticon {
display: block;
float: left;
line-height: 16px;
}
}
}
.ant-list {
&.ks-algorithm-list {
.ant-list-item {
cursor: pointer;
transition: all 0.5s;
border-left: 2px solid transparent;
position: relative;
&.selected,
&:hover {
background: #0a1b3c;
border-left: 2px solid #11377e;
}
}
.ks-algorithm-type {
position: absolute;
right: 10px;
.ant-badge {
.ant-badge-count {
color: #c3c2c2;
background: #333f7d;
box-shadow: 0 0 0 1px #325478;
}
}
}
.ant-list-item-meta {
.ant-list-item-meta-title {
color: #7ae8fc;
}
.ant-list-item-meta-description {
color: #4d8c98;
font-size: 13px;
}
}
}
}
.ks-algorithm-param-list {
padding: 15px;
border: 1px solid #475f71;
border-radius: 2px;
.ks-algorithm-param-item {
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
}
.ks-algorithm-param-actions {
.anticon {
color: #7ae8fc;
font-size: 20px;
display: block;
line-height: 26px;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,54 @@
/*
* 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';
export interface AlgorithmParam {
id: number,
algorithmId: number,
// 参数名
paramName: NullableString,
// 默认值
defaultValue: NullableString,
// 描述
description: NullableString,
}
export interface AlgorithmConfig {
name: NullableString,
operation: NullableString,
}
export interface Algorithm {
id: number,
// 算法名称
name: NullableString,
// 业务类型
type: NullableString | undefined,
// 算法文件路径
codePath: NullableString,
// 算法描述
description: NullableString,
// 算法配置
algoConfig: NullableString,
// 算法配置
algoConfigList: AlgorithmConfig[],
// 算法参数定义信息
algorithmParamList: AlgorithmParam[]
}
export interface AlgorithmRequest extends Algorithm {
pageNum: number,
pageSize: number,
}
export interface AlgorithmPageableResponse extends PageableResponse<Algorithm> {
}

View File

@@ -8,7 +8,7 @@
*/
import { HttpRequestClient } from '@/utils/request';
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, NodeTemplatesResponse } from './types';
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, BehaviorTreeRequest, NodeTemplatesResponse } from './types';
import type { BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({
@@ -19,7 +19,7 @@ export const findNodeTemplates = (): Promise<NodeTemplatesResponse> => {
return req.get('/system/nodetemplate/all');
};
export const findTreesByQuery = (query: Partial<BehaviorTree> = {}): Promise<BehaviorTreePageResponse> => {
export const findTreesByQuery = (query: Partial<BehaviorTreeRequest> = {}): Promise<BehaviorTreePageResponse> => {
return req.get<BehaviorTreePageResponse>('/system/behaviortree/list', query);
};
@@ -27,6 +27,9 @@ export const findOneTreeById = (id: number): Promise<BehaviorTreeDetailsResponse
return req.get(`/system/behaviortree/${id}`);
};
export const deleteOneTreeById = (id: number): Promise<BehaviorTreeDetailsResponse> => {
return req.delete(`/system/behaviortree/${id}`);
};
export const updateTree = (rt: Partial<BehaviorTree>): Promise<BasicResponse> => {
return req.postJson(`/system/behaviortree`, rt);
@@ -35,18 +38,3 @@ export const updateTree = (rt: Partial<BehaviorTree>): Promise<BasicResponse> =>
export const createTree = (rt: Partial<BehaviorTree>): Promise<BasicResponse> => {
return req.putJson(`/system/behaviortree`, rt);
};
// export const findOneTreeById = (id: number): Promise<TreeModelDetailsResponse> => {
// return req.postJson(`/tree-details`,{
// tree_id: id
// });
// };
//
// export const updateTree = (rt: Partial<TreeModelGraph>): Promise<BasicResponse> => {
// return req.postJson('/update-tree', rt);
// };
//
// export const createTree = (rt: Partial<TreeModelGraph>): Promise<BasicResponse> => {
// return req.postJson('/update-tree', rt);
// };

View File

@@ -8,7 +8,6 @@
*/
import type { NullableString } from '@/types';
import type { NodeSetting } from '@/views/decision/types';
export interface DraggableElement {
id: number | null,
@@ -19,12 +18,23 @@ export interface DraggableElement {
draggable: boolean,
parent?: DraggableElement,
children: DraggableElement[]
[key: string]: unknown;
}
export type ElementStatus = 'default' | 'success' | 'failed' | 'running' | string | null
export interface ElementPosition {
export interface ElementParameter {
id: number,
templateId: number,
paramKey: NullableString,
dataType: NullableString,
defaultValue: NullableString,
description: NullableString,
templateType: NullableString,
}
export interface GraphPosition {
x: number;
y: number;
}
@@ -37,57 +47,57 @@ export interface ElementVariable {
unit: NullableString;
}
export interface BaseElement {
key: string;
name: string;
type: string;
width: number;
height: number;
position: ElementPosition;
category: NullableString;
element?: DraggableElement;
[key: string]: unknown;
}
export interface TaskNodeRect {
export interface GraphTaskRect {
width?: number;
height?: number;
x?: number;
y?: number;
}
export interface TaskNodeElement extends BaseElement {
template: number;
inputs: any;
outputs: any;
variables: ElementVariable[];
parameters: Record<any, any>;
children?: TaskNodeElement[],
export interface BaseElement {
id: number;
key: NullableString;
name: NullableString;
description: NullableString;
type: NullableString;
width: number;
height: number;
position: GraphPosition;
category: NullableString;
element?: DraggableElement;
[key: string]: unknown;
}
export interface SettingTaskNodeElement extends TaskNodeElement {
settings: NodeSetting[];
export interface GraphTaskElement extends BaseElement {
template: number;
templateType: NullableString,
inputs: any;
outputs: any;
variables: ElementVariable[];
parameters: ElementParameter[];
children?: GraphTaskElement[],
[key: string]: unknown;
}
export interface ModelElement extends BaseElement {
edges: EdgeNodeElement[];
edges: GraphEdgeElement[];
}
export interface EdgeNodeElement {
export interface GraphEdgeElement {
id: number;
key: NullableString;
source: NullableString;
sourceName: NullableString;
target: NullableString;
targetName: NullableString;
attrs: Record<any, any>;
router: Record<any, any>;
connector: any;
[key: string]: unknown;
}
export interface NodeGraph {
edges: EdgeNodeElement[];
nodes: TaskNodeElement[];
edges: GraphEdgeElement[];
nodes: GraphTaskElement[];
}

View File

@@ -10,7 +10,7 @@
import { Edge, Graph, Path, Selection } from '@antv/x6';
import type { ModelElement } from './element';
import type { Connecting } from '@antv/x6/lib/graph/options';
import {createLineOptions} from './line'
import { createLineOptions } from './line';
Graph.registerConnector(
'sequenceFlowConnector',
@@ -55,7 +55,7 @@ export const createGraphConnectingAttributes = (): Partial<Connecting> => {
attrs: lineOptions.attrs,
animation: lineOptions.animation,
markup: lineOptions.markup,
})
});
return edge;
},
validateConnection(this: Graph, { sourceCell, targetCell }) {

View File

@@ -13,6 +13,7 @@ import type { NodeViewPositionEventArgs } from '@antv/x6/es/view/node/type';
import { createGraphCanvas } from './graph';
import { EventListener } from '@/utils/event';
import type { ModelElement } from './element';
// import {createLineOptions} from './line'
export interface UseGraphCanvas {
@@ -155,6 +156,7 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => {
if (!existingEdge) {
sourceEdges.push({
id: sourceData.id,
key: edge.id,
source: sourceNode.id,
sourceName: sourceData.name,
@@ -162,7 +164,7 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => {
router: {},
attrs: {},
target: targetNode.id,
targetName: targetData.name
targetName: targetData.name,
});
sourceNode.replaceData({ ...sourceData, edges: sourceEdges });
}

View File

@@ -43,5 +43,5 @@ export const createLineOptions = (): any => {
},
],
],
}
}
};
};

View File

@@ -9,64 +9,25 @@
>
<template #title>
<a-space>
<span class="ks-designer-node-icon"></span>
<span class="ks-designer-node-title">{{ element?.name ?? '-' }}</span>
</a-space>
</template>
<!-- 节点内容区域 -->
<div class="port port-in" data-port="in-0" magnet="passive"></div>
<div class="w-full">
<div class="ks-designer-node-content">
<div
v-for="(item, index) in element?.children || []"
:key="item.id || index"
class="ks-designer-node-row"
>
<div
:data-port="`in-${item.id || index}`"
:title="`入桩: ${item.name}`"
class="port port-in"
magnet="passive"
></div>
<!-- child名称 -->
<div class="ks-designer-node-name">
{{ item.name }}
</div>
<!-- 右侧出桩只能作为连线源 -->
<div
:data-port="`out-${item.id || index}`"
:title="`出桩: ${item.name}`"
class="port port-out"
magnet="active"
></div>
</div>
<div v-if="!(element?.children && element?.children?.length > 0)" class="ks-designer-node-row">
<div class="port port-in" data-port="in-0" magnet="passive"></div>
<div class="ks-designer-node-name" v-if="element?.category !== 'component'">
{{ element?.name ?? '-' }}
</div>
<div class="ks-designer-node-name" v-else>
<p>隐藏纬度: {{ element?.parameters?.hiddenLatitude ?? '-' }}</p>
<p>激活函数: {{ element?.parameters?.activationFunction ?? '-' }}</p>
</div>
<div class="port port-out" data-port="out-0" magnet="active"></div>
</div>
</div>
<a-tooltip v-if="(element?.description ?? (element?.name ?? '-')).length >= 38">
<template #title>
{{ element?.description ?? element?.name }}
</template>
<p>
{{ substring(element?.description ?? (element?.name ?? '-'), 40) }}
</p>
</a-tooltip>
<p v-else>
{{ substring(element?.description ?? (element?.name ?? '-'), 40) }}
</p>
</div>
<!-- <div class="w-full" v-else>-->
<!-- <div class="ks-designer-node-content">-->
<!-- <div class="port port-in" data-port="in-0" magnet="passive"></div>-->
<!-- <div class="ks-designer-node-name">-->
<!-- <p>隐藏纬度: {{ element?.parameters?.hiddenLatitude ?? '-' }}</p>-->
<!-- <p>激活函数: {{ element?.parameters?.activationFunction ?? '-' }}</p>-->
<!-- </div>-->
<!-- <div class="port port-out" data-port="out-0" magnet="active"></div>-->
<!-- </div>-->
<!-- </div>-->
<div class="port port-out" data-port="out-0" magnet="active"></div>
</a-card>
<template #overlay>
@@ -88,6 +49,7 @@ import { elementProps } from './props';
import type { ModelElement } from './element';
import { DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
import type { Graph } from '@antv/x6';
import { substring } from '@/utils/strings';
export default defineComponent({
name: 'ModelElement',
@@ -157,6 +119,7 @@ export default defineComponent({
return {
element,
substring,
handleMenuClick,
handleVisibleChange,
};
@@ -166,23 +129,37 @@ export default defineComponent({
<style lang="less">
.ks-designer-node {
background: linear-gradient(150deg, #093866 1%, #1f69b3 55%);
border: 0;
background: linear-gradient(150deg, rgba(108, 99, 255) 1%, rgba(108, 99, 255) 100%);
border-radius: 8px;
width: 100%;
height: 100%;
cursor: pointer;
position: relative;
position: relative;
background: #1e2533;
border: 1px solid #4a7aff;
border: 2px solid #000000;
&:hover {
border: 2px solid #4a7aff;
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
}
.ant-card-head {
border: 0;
height: 38px;
min-height: 38px;
height: 25px;
min-height: 25px;
border-radius: 0;
color: #ddd;
color: #fff;
font-size: 12px;
font-weight: normal;
padding: 0 20px;
background: linear-gradient(to bottom, #3a4c70, #2d3a56);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background: linear-gradient(to bottom, rgba(108, 99, 255, 0.15), rgba(108, 99, 255, 0.05));
background: url('@/assets/icons/bg-node-head.png') center / 100% 100%;
//background: linear-gradient(to bottom, rgb(234 234 234 / 20%), rgb(191 191 191 / 58%));
}
.ks-designer-node-icon {
@@ -197,136 +174,163 @@ export default defineComponent({
.ks-designer-node-title {
font-size: 13px;
color: #fff;
color: #312e2e;
}
.ant-card-body {
color: #fff;
height: calc(100% - 38px);
color: #f5f5f5;
height: calc(100% - 25px);
border-radius: 0;
font-size: 12px;
padding: 8px 15px;
overflow-y: auto;
border-top: 1px solid #195693;
padding: 8px 15px !important;
border-top: 1px solid rgba(108, 99, 255, 0.5);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
white-space: normal; // 恢复默认的换行行为
word-wrap: break-word; // 允许长单词换行
word-break: break-all; // 允许在任意字符处换行
line-height: 1.4; // 增加行高提升可读性
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
}
&.ks-designer-task-node {
background: linear-gradient(150deg, #20421b 1%, #4a6646 55%);
.ant-card-body {
border-top: 1px solid #466741;
}
.ks-designer-node-icon {
background: url('@/assets/icons/m-02.png') center / 100% 100%;
}
}
&.ks-designer-input-node {
background: linear-gradient(150deg, #083058 1%, #1e5d9b 55%);
.ant-card-body {
border-top: 1px solid #105ca7;
}
.ks-designer-node-icon {
background: url('@/assets/icons/icon-model-input.png') center / 100% 100%;
}
}
&.ks-designer-action-node {
background: linear-gradient(150deg, #343207 1%, #485010 55%);
.ant-card-body {
border-top: 1px solid #59550e;
}
.ks-designer-node-icon {
background: url('@/assets/icons/bg-fk-point.png') center / 100% 100%;
}
}
&.ks-designer-component-node {
background: linear-gradient(150deg, #06226b 1%, #1a43a7 55%);
.ant-card-body {
border-top: 1px solid #26448c;
}
}
&.ks-designer-control-node {
background: linear-gradient(150deg, #1d4f32 1%, #326a5d 55%);
.ant-card-body {
border-top: 1px solid #326a5d;
}
.ks-designer-node-icon {
background: url('@/assets/icons/bg-model-builder-card-title.png') center / 100% 100%;
}
}
// model/task 节点:浅紫渐变
//&.ks-designer-model-node,
//&.ks-designer-task-node {
// background: linear-gradient(150deg, rgba(92,84,247,0.9) 1%, rgba(115,108,250,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(92,84,247,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/m-02.png') center / 100% 100%;
// }
//}
//
//// input 节点:深紫渐变
//&.ks-designer-input-node {
// background: linear-gradient(150deg, rgba(82,73,245,0.9) 1%, rgba(105,98,249,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(82,73,245,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/icon-model-input.png') center / 100% 100%;
// }
//}
//
//// action 节点亮紫渐变对应之前的action-node
//&.ks-designer-action-node {
// background: linear-gradient(150deg, rgba(108,99,255,0.9) 1%, rgba(140,133,255,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(108,99,255,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/bg-fk-point.png') center / 100% 100%;
// }
//}
//
//// precondition/component 节点:蓝紫渐变
//&.ks-designer-precondition-node,
//&.ks-designer-component-node {
// background: linear-gradient(150deg, rgba(72,64,243,0.9) 1%, rgba(95,88,248,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(72,64,243,0.5);
// }
//}
//
//// select/control 节点:浅蓝紫渐变
//&.ks-designer-select-node,
//&.ks-designer-control-node {
// background: linear-gradient(150deg, rgba(90,82,246,0.9) 1%, rgba(118,111,251,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(90,82,246,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/bg-model-builder-card-title.png') center / 100% 100%;
// }
//}
// 连接桩容器样式
.ks-designer-node-content {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px; // 每个child行之间的间距
gap: 4px;
}
// 每个child行包含左右桩+文本)
.ks-designer-node-row {
width: 100%;
display: flex;
align-items: center;
position: relative;
min-height: 24px; // 固定行高,保证桩对齐
min-height: 24px;
}
// 连接桩基础样式
.port {
width: 12px;
height: 12px;
border-radius: 50%;
cursor: crosshair;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgb(74 114 214 / 80%);
z-index: 10; // 确保桩在最上层
// X6 标记为可连线的磁体
box-shadow: 0 0 0 2px rgb(108, 99, 255, 0.8);
z-index: 10;
magnet: true;
}
// 左侧入桩样式
.port-in {
background-color: #093866; // 青色:入桩
margin-right: 8px; // 与文本的间距
background-color: #6C63FF;
margin-right: 8px;
//border: 1px solid #093866;
// X6 只能作为连线目标(入)
magnet: passive;
box-shadow: none;
width: 20px;
height: 20px;
width: 15px;
height: 15px;
display: block;
background: url('@/assets/icons/point.svg') center / 100% 100%;
position: absolute;
//top: 7px;
left: -8px;
top: 50%;
}
// 右侧出桩样式
.port-out {
margin-left: 8px; // 与文本的间距
margin-left: 8px;
margin-right: 5px;
// X6 只能作为连线源(出)
magnet: active;
box-shadow: none;
width: 20px;
height: 20px;
width: 15px;
height: 15px;
display: block;
background: url('@/assets/icons/arrow-right.svg') center / 100% 100%;
background: url('@/assets/icons/point.svg') center / 100% 100%;
position: absolute;
//right: 8px;
//top: 7px;
top: 50%;
right: -12px;
}
// 节点文本样式
.ks-designer-node-name {
flex: 1; // 占满中间空间
flex: 1;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
//white-space: nowrap;
}
}
</style>

View File

@@ -21,7 +21,7 @@ import ModelElement from './node.vue';
export const registerNodeElement = () => {
console.info('registerNodeElement');
register({
shape: 'node',
shape: 'task',
component: ModelElement,
width: 120,
attrs: {

View File

@@ -6,20 +6,20 @@
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { EdgeNodeElement, NodeGraph, TaskNodeElement } from './element';
import type { GraphEdgeElement, GraphTaskElement, NodeGraph } from './element';
import { Edge, Graph, Node } from '@antv/x6';
export const defaultHeight: Record<string, number> = {
component: 110,
};
export const createModelNode = (element: TaskNodeElement, width: number = 250, height: number = 120): any => {
export const createGraphTaskElement = (element: GraphTaskElement, width: number = 250, height: number = 120): any => {
let realHeight = defaultHeight[element.category as string];
if (!realHeight) {
realHeight = 120;
}
return {
shape: 'node',
shape: 'task',
id: element.key,
position: {
x: element.position?.x || 0,
@@ -38,13 +38,13 @@ export const createModelNode = (element: TaskNodeElement, width: number = 250, h
};
};
export const resolveNodeTaskElements = (graph: Graph): TaskNodeElement[] => {
const taskElements: TaskNodeElement[] = [];
export const resolveGraphTaskElements = (graph: Graph): GraphTaskElement[] => {
const taskElements: GraphTaskElement[] = [];
if (graph) {
const nodes = graph?.getNodes() as Node[];
if (nodes) {
nodes.forEach(node => {
const nodeData = node.getData() as TaskNodeElement;
const nodeData = node.getData() as GraphTaskElement;
const newElement = {
...nodeData,
key: node.id,
@@ -59,13 +59,13 @@ export const resolveNodeTaskElements = (graph: Graph): TaskNodeElement[] => {
return taskElements;
};
export const resolveNodeEdgeElements = (graph: Graph): EdgeNodeElement[] => {
const edgeElements: EdgeNodeElement[] = [];
export const resolveGraphEdgeElements = (graph: Graph): GraphEdgeElement[] => {
const edgeElements: GraphEdgeElement[] = [];
if (graph) {
const graphEdges = graph?.getEdges() ?? [] as Edge[];
if (graphEdges) {
graphEdges.forEach(edge => {
const nodeData = edge.getData() as TaskNodeElement;
const nodeData = edge.getData() as GraphTaskElement;
edgeElements.push({
id: nodeData?.id ?? 0,
key: edge.id,
@@ -84,8 +84,8 @@ export const resolveNodeEdgeElements = (graph: Graph): EdgeNodeElement[] => {
};
export const resolveNodeGraph = (graph: Graph): NodeGraph => {
const nodes: TaskNodeElement[] = resolveNodeTaskElements(graph);
const edges: EdgeNodeElement[] = resolveNodeEdgeElements(graph);
const nodes: GraphTaskElement[] = resolveGraphTaskElements(graph);
const edges: GraphEdgeElement[] = resolveGraphEdgeElements(graph);
return {
nodes,
edges,
@@ -94,7 +94,7 @@ export const resolveNodeGraph = (graph: Graph): NodeGraph => {
export const hasElements = (graph: Graph): boolean => {
if (graph) {
const taskElements: TaskNodeElement[] = resolveNodeTaskElements(graph);
const taskElements: GraphTaskElement[] = resolveGraphTaskElements(graph);
return taskElements.length > 0;
}
return false;
@@ -102,7 +102,7 @@ export const hasElements = (graph: Graph): boolean => {
export const hasRootElementNode = (graph: Graph): boolean => {
if (graph) {
const taskElements: TaskNodeElement[] = resolveNodeTaskElements(graph);
const taskElements: GraphTaskElement[] = resolveGraphTaskElements(graph);
return taskElements.filter(e => e.type === 'root').length === 1;
}
return false;

View File

@@ -6,6 +6,8 @@
<div class="ks-model-builder-body">
<div class="ks-model-builder-left">
<TressCard
ref="treesCardRef"
@create-tree="handleCreateTree"
@select-tree="handleSelectTree"
/>
<NodesCard
@@ -17,20 +19,24 @@
<div class="ks-model-builder-content">
<div class="ks-model-builder-actions">
<a-space>
<a-tooltip v-if="graph && currentBehaviorTree" placement="top">
<template #title>
保存
</template>
<a-popconfirm
title="确定保存?"
@confirm="handleSave"
>
<a-button class="ks-model-builder-save" size="small">
<CheckOutlined />
<span>保存</span>
</a-button>
</a-popconfirm>
</a-tooltip>
<!-- <a-tooltip v-if="graph && currentBehaviorTree" placement="top">-->
<!-- <template #title>-->
<!-- 保存-->
<!-- </template>-->
<!-- <a-popconfirm-->
<!-- title="确定保存?"-->
<!-- @confirm="handleSave"-->
<!-- >-->
<!-- <a-button class="ks-model-builder-save" size="small">-->
<!-- <CheckOutlined />-->
<!-- <span>保存</span>-->
<!-- </a-button>-->
<!-- </a-popconfirm>-->
<!-- </a-tooltip>-->
<a-button v-if="graph && currentBehaviorTree" class="ks-model-builder-save" size="small" @click="handleSave">
<CheckOutlined />
<span>保存</span>
</a-button>
</a-space>
</div>
<!-- 画布容器添加拖放事件 -->
@@ -46,10 +52,12 @@
</div>
<Properties
v-if="graph"
@update-element="handleUpdateElement"
:element="selectedNodeTaskElement"
:graph="graph as any"
:node="selectedModelNode as any" />
:node="selectedModelNode as any"
:tree="currentBehaviorTree"
:tree-editing="currentTreeEditing"
@update-element="handleUpdateElement" />
</div>
</a-layout>
</a-layout>
@@ -66,16 +74,16 @@ import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import Header from './header.vue';
import Properties from './properties.vue';
import type { BehaviorTree, NodeTemplate } from './types';
import type { GraphTaskElement, NodeGraph } from './builder/element';
import { useGraphCanvas } from './builder/hooks';
import { registerNodeElement } from './builder/register';
import type { BehaviorTree, NodeTemplate } from './types';
import type { NodeGraph, SettingTaskNodeElement, TaskNodeElement } from './builder/element';
import { createLineOptions } from './builder/line';
import { createTree, findOneTreeById, updateTree } from './api';
import { createModelNode, hasElements, hasRootElementNode, resolveNodeGraph } from './builder/utils';
import { createTaskNodeElementFromTemplate } 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';
import { createLineOptions } from '@/views/decision/builder/line.ts';
const TeleportContainer = defineComponent(getTeleport());
@@ -100,11 +108,13 @@ export default defineComponent({
const currentZoom = ref<number>(1);
const draggedNodeData = ref<NodeTemplate | 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<SettingTaskNodeElement | null>(null);
const changed = ref<boolean>(false)
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
const changed = ref<boolean>(false);
const treesCardRef = ref<InstanceType<typeof TressCard> | null>(null);
const {
handleGraphEvent,
@@ -154,6 +164,7 @@ export default defineComponent({
safePreventDefault(e);
safeStopPropagation(e);
isDraggingOver.value = false;
currentTreeEditing.value = false;
if (!currentBehaviorTree.value) {
message.error('请先选择或者创建行为树.');
@@ -188,9 +199,9 @@ export default defineComponent({
console.log('放置节点:', { ...template, x, y });
// 创建节点数据
const settingTaskElement: SettingTaskNodeElement = createTaskNodeElementFromTemplate(template, { x, y });
const settingTaskElement: GraphTaskElement = createGraphTaskElementFromTemplate(template, { x, y });
// 创建节点
const settingTaskNode = createModelNode(settingTaskElement);
const settingTaskNode = createGraphTaskElement(settingTaskElement);
console.info('create settingTaskNode: ', settingTaskElement, settingTaskNode);
// 将节点添加到画布
@@ -224,6 +235,7 @@ export default defineComponent({
...r.data,
graph: nodeGraph,
};
currentTreeEditing.value = true;
createElements();
} else {
message.error(r.msg ?? '行为树不存在.');
@@ -233,31 +245,61 @@ export default defineComponent({
const createElements = () => {
nextTick(() => {
graph.value?.clearCells();
if (currentBehaviorTree.value?.graph && graph.value) {
if (currentBehaviorTree.value?.graph.nodes) {
currentBehaviorTree.value?.graph.nodes.forEach(ele => {
const node = createModelNode(ele as TaskNodeElement);
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); // 延迟一会儿,免得连线错位
}
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) {
@@ -275,12 +317,18 @@ export default defineComponent({
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 SettingTaskNodeElement;
const newElement = node.getData() as GraphTaskElement;
selectedModelNode.value = node;
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as SettingTaskNodeElement;
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as GraphTaskElement;
});
// 监听节点鼠标事件,显示/隐藏连接点
@@ -310,7 +358,7 @@ export default defineComponent({
});
};
const handleUpdateElement = (element: SettingTaskNodeElement) => {
const handleUpdateElement = (element: GraphTaskElement) => {
// 更新选中的节点数据
if (selectedModelNode.value) {
selectedModelNode.value.replaceData(element);
@@ -318,7 +366,7 @@ export default defineComponent({
console.info('handleUpdateElement', element);
// 更新本地引用
selectedNodeTaskElement.value = element;
changed.value = true
changed.value = true;
};
const handleSave = () => {
@@ -333,6 +381,14 @@ export default defineComponent({
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);
@@ -341,6 +397,7 @@ export default defineComponent({
}
res.then(r => {
if (r.code === 200) {
treesCardRef.value?.refresh();
message.success(r.msg ?? '操作成功.');
} else {
message.error(r.msg ?? '操作失败.');
@@ -368,6 +425,9 @@ export default defineComponent({
});
return {
treesCardRef,
handleCreateTree,
currentTreeEditing,
currentBehaviorTree,
currentNodeGraph,
selectedNodeTaskElement,

View File

@@ -7,7 +7,7 @@
</template>
<div class="w-full h-full">
<a-row>
<a-col :span="12" v-for="nm in controlTemplates">
<a-col v-for="nm in controlTemplates" :span="12">
<div
:key="nm.id"
:data-type="nm.type"
@@ -15,7 +15,7 @@
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
>
<img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''" />
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
<span class="desc">{{ nm.name }}</span>
</div>
</a-col>
@@ -28,7 +28,7 @@
</template>
<div class="w-full h-full">
<a-row>
<a-col :span="12" v-for="nm in conditionTemplates">
<a-col v-for="nm in conditionTemplates" :span="12">
<div
:key="nm.id"
:data-type="nm.type"
@@ -36,7 +36,7 @@
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
>
<img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''" />
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
<span class="desc">{{ nm.name }}</span>
</div>
</a-col>
@@ -49,7 +49,7 @@
</template>
<div class="w-full h-full">
<a-row>
<a-col :span="12" v-for="nm in actionsTemplates">
<a-col v-for="nm in actionsTemplates" :span="12">
<div
:key="nm.id"
:data-type="nm.type"
@@ -57,7 +57,7 @@
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
>
<img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''" />
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
<span class="desc">{{ nm.name }}</span>
</div>
</a-col>
@@ -161,6 +161,6 @@ export default defineComponent({
handleDragEnd,
};
},
})
});
</script>

View File

@@ -1,12 +1,35 @@
<template>
<div class="ks-model-builder-right">
<template v-if="currentElement">
<template v-if="currentElement || tree">
<a-tabs v-model:activeKey="activeTopTabsKey" class="ks-model-builder-tabs settings-tab">
<template #leftExtra>
<span class="ks-model-builder-title-icon icon-input"></span>
</template>
<a-tab-pane key="1" tab="节点属性">
<a-tab-pane v-if="tree" key="1" tab="行为树属性">
<a-form
autocomplete="off"
layout="vertical"
name="basic"
style="padding-bottom:15px;"
>
<a-form-item label="行为树名称">
<a-input v-model:value="tree.name" placeholder="行为树名称" size="small" />
</a-form-item>
<a-form-item label="行为树英文名称">
<a-input v-model:value="tree.englishName" placeholder="行为树英文名称" size="small" />
</a-form-item>
<a-form-item label="行为树说明">
<a-textarea v-model:value="tree.description" placeholder="行为树说明" size="small" />
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane v-if="currentElement" key="2" tab="节点属性">
<a-form
autocomplete="off"
layout="vertical"
@@ -14,41 +37,35 @@
style="padding-bottom:15px;"
>
<a-form-item label="节点名称">
<a-input size="small" v-model:value="currentElement.name" :placeholder="currentElement.name" />
<a-input v-model:value="currentElement.name" :placeholder="currentElement.name" size="small" />
</a-form-item>
<a-form-item label="节点介绍">
<a-textarea size="small" v-model:value="currentElement.description" :placeholder="currentElement.description" />
<a-textarea v-model:value="currentElement.description" :placeholder="currentElement.description" size="small" />
</a-form-item>
<a-divider />
<a-form-item label="输入">
<a-textarea size="small" v-model:value="currentElement.inputs" />
<a-textarea v-model:value="currentElement.inputs" size="small" />
</a-form-item>
<a-form-item label="输出">
<a-textarea size="small" v-model:value="currentElement.outputs" />
<a-textarea v-model:value="currentElement.outputs" size="small" />
</a-form-item>
<a-divider v-if="currentElement.settings && currentElement.settings.length > 0"/>
<a-divider v-if="currentElement.settings && currentElement.parameters.length > 0" />
<a-form-item :label="setting.description" v-for="setting in currentElement.settings">
<a-input-number size="small" style="width:100%;" v-if="setting.data_type === 'double'" v-model:value="setting.default_value" :placeholder="setting.description" />
<a-input v-else size="small" v-model:value="setting.default_value" :placeholder="setting.description" />
<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-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
</a-form-item>
</a-form>
</a-tab-pane>
<!-- <a-tab-pane key="2" tab="外观">-->
<!-- </a-tab-pane>-->
<!-- <a-tab-pane key="3" tab="系统">-->
<!-- </a-tab-pane>-->
</a-tabs>
<a-tabs v-model:activeKey="activeBottomTabsKey" class="ks-model-builder-tabs parameters-tabs">
<a-tabs v-if="currentElement" v-model:activeKey="activeBottomTabsKey" class="ks-model-builder-tabs parameters-tabs">
<template #leftExtra>
<span class="ks-model-builder-title-icon icon-input"></span>
</template>
@@ -83,7 +100,7 @@
</a-button>
</template>
<template v-else>
<a-input v-model:value="record[column.dataIndex]" size="small"/>
<a-input v-model:value="record[column.dataIndex]" size="small" />
</template>
</template>
</a-table>
@@ -111,9 +128,10 @@
<script lang="ts">
import { defineComponent, onMounted, type PropType, ref, watch } from 'vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import type { ElementVariable, SettingTaskNodeElement } from './builder/element';
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'
import { generateKey } from '@/utils/strings';
const actionSpaceColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', width: 40 },
@@ -126,17 +144,21 @@ const actionSpaceColumns = [
export default defineComponent({
components: { CheckOutlined },
props: {
tree: { type: [Object, null] as PropType<BehaviorTree | null | undefined>, required: false },
treeEditing: { type: Boolean as PropType<boolean>, required: true, default: false },
node: { type: [Object, null] as PropType<Node<NodeProperties> | null | undefined>, required: false },
graph: { type: [Object, null] as PropType<Graph | null | undefined>, required: true }
graph: { type: [Object, null] as PropType<Graph | null | undefined>, required: true },
},
emits: ['update-element'],
emits: ['update-element', 'update-tree'],
setup(props, { emit }) {
const activeTopTabsKey = ref<string>('1');
const activeBottomTabsKey = ref<string>('1');
const activeBottomTabs2Key = ref<string>('1');
const currentTree = ref<BehaviorTree | null>(props.tree ?? null);
const treeEditing = ref<boolean>(props.treeEditing);
const currentNode = ref<Node | null>(props.node ?? null);
const currentElement = ref<SettingTaskNodeElement | null>(null);
const currentElement = ref<GraphTaskElement | null>(null);
const load = () => {
};
@@ -145,13 +167,13 @@ export default defineComponent({
currentNode.value = n ?? null;
if (n) {
const data = n.getData();
currentElement.value = JSON.parse(JSON.stringify(data || {})) as SettingTaskNodeElement;
currentElement.value = JSON.parse(JSON.stringify(data || {})) as GraphTaskElement;
} else {
currentElement.value = null;
}
};
const addVariable = ()=> {
const addVariable = () => {
if (!currentElement.value) {
return;
}
@@ -165,8 +187,8 @@ export default defineComponent({
value: null,
defaults: null,
unit: null,
})
}
});
};
const removeVariable = (row: ElementVariable) => {
if (currentElement.value && currentElement.value.variables) {
@@ -178,7 +200,7 @@ export default defineComponent({
const updateNode = () => {
if (currentNode.value && currentElement.value) {
// 深拷贝当前元素数据
const newElement = JSON.parse(JSON.stringify(currentElement.value)) as SettingTaskNodeElement;
const newElement = JSON.parse(JSON.stringify(currentElement.value)) as GraphTaskElement;
// 更新节点数据
currentNode.value.replaceData(newElement);
// 触发事件通知父组件
@@ -192,6 +214,22 @@ export default defineComponent({
{ deep: true, immediate: true },
);
watch(
() => props.tree,
(n?: BehaviorTree | null | undefined) => {
currentTree.value = n ?? null;
},
{ deep: true, immediate: true },
);
watch(
() => props.treeEditing,
(n?: boolean | null | undefined) => {
treeEditing.value = n === true;
},
{ deep: true, immediate: true },
);
watch(() => currentElement.value, () => updateNode(), { deep: true });
onMounted(() => load());
@@ -204,6 +242,8 @@ export default defineComponent({
currentElement,
addVariable,
removeVariable,
currentTree,
treeEditing,
// currentElementParameters,
};
},

View File

@@ -1,45 +1,97 @@
<template>
<a-collapse v-model:activeKey="activeKey" :accordion="false">
<a-collapse-panel key="1">
<a-collapse v-model:activeKey="activeKey" :accordion="false" class="ks-trees-collapse">
<a-collapse-panel key="1" style="position: relative">
<template #header>
<span class="ks-model-builder-title-icon icon-model"></span>我的行为树
<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>
<a-list size="small" :data-source="behaviorTrees || []" style="min-height: 25vh">
<div class="w-full" style="padding: 5px;">
<a-flex>
<a-input-search v-model:value="behaviorTreeQuery.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="behaviorTrees || []" size="small" style="min-height: 25vh">
<template #renderItem="{ item }">
<a-tooltip placement="right">
<template #title>
<p>名称: {{ item.name }}</p>
<p>说明: {{item.description}}</p>
</template>
<a-list-item @click="()=> handleSelect(item)">
{{ substring(item.name, 15) }}
</a-list-item>
</a-tooltip>
<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="behaviorTreeQuery.pageNum"
:page-size="behaviorTreeQuery.pageSize"
:total="totalTress"
simple size="small" @change="handleChange" />
</a-collapse-panel>
</a-collapse>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import type { BehaviorTree } from './types';
import { findTreesByQuery } from './api';
import {substring} from '@/utils/strings'
import { CheckOutlined, DeleteOutlined, EditFilled, PlusOutlined } from '@ant-design/icons-vue';
import type { BehaviorTree, BehaviorTreeRequest } from './types';
import { deleteOneTreeById, findTreesByQuery } from './api';
import { substring } from '@/utils/strings';
export default defineComponent({
emits: ['select-tree'],
emits: ['select-tree', 'create-tree'],
components: {
PlusOutlined
CheckOutlined,
PlusOutlined,
DeleteOutlined,
EditFilled,
},
setup(_props, { emit }) {
const behaviorTrees = ref<BehaviorTree[]>([]);
const behaviorTreeQuery = ref<Partial<BehaviorTree>>({});
const activeKey = ref<number>(1)
const behaviorTreeQuery = ref<Partial<BehaviorTreeRequest>>({
name: null,
pageNum: 1,
pageSize: 8,
});
const activeKey = ref<number>(1);
const totalTress = ref<number>(0);
const loadTress = () => {
findTreesByQuery(behaviorTreeQuery.value).then(r => {
behaviorTrees.value = r.rows;
totalTress.value = r.total ?? 0;
});
};
const handleChange = (page: number, pageSize: number) => {
behaviorTreeQuery.value.pageNum = page;
behaviorTreeQuery.value.pageSize = pageSize;
loadTress();
};
const handleDelete = (item: BehaviorTree) => {
deleteOneTreeById(item.id).then(r => {
if (r.code === 200) {
loadTress();
}
});
};
@@ -52,7 +104,7 @@ export default defineComponent({
const handleSelect = (record: BehaviorTree) => {
emit('select-tree', record);
}
};
const customRow = (record: BehaviorTree) => {
return {
@@ -62,11 +114,15 @@ export default defineComponent({
};
};
const refresh = () => loadTress();
onMounted(() => {
loadTress();
});
return {
refresh,
totalTress,
substring,
activeKey,
behaviorTrees,
@@ -75,19 +131,57 @@ export default defineComponent({
columns,
customRow,
handleSelect,
handleChange,
handleDelete,
};
},
})
});
</script>
<style lang="less" scoped>
.create-tree-icon{
<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: 5px 5px;
padding: 3px 5px;
cursor: pointer;
color: rgb(130 196 233);

View File

@@ -7,6 +7,5 @@
* that was distributed with this source code.
*/
export * from './tree'
export * from './template'
export * from './parameter'
export * from './tree';
export * from './template';

View File

@@ -1,20 +0,0 @@
/*
* 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 } from '@/types';
export interface NodeSetting {
id: number;
name: NullableString;
type: NullableString;
default_value: NullableString;
data_type: NullableString;
required: boolean;
description: NullableString;
}

View File

@@ -8,16 +8,7 @@
*/
import type { ApiDataResponse, NullableString } from '@/types';
export interface NodeTemplateParameter {
id: number,
templateId: number,
paramKey: NullableString,
dataType: NullableString,
defaultValue: NullableString,
description: NullableString,
templateType: NullableString,
}
import type { ElementParameter } from '../builder/element';
export interface NodeTemplate {
id: number;
@@ -25,9 +16,9 @@ export interface NodeTemplate {
type: NullableString;
logicHandler: NullableString;
description: NullableString;
templeteType: NullableString;
templateType: NullableString;
englishName: NullableString;
parameters: NodeTemplateParameter[],
parameters: ElementParameter[],
}
export interface NodeTemplatesResponse extends ApiDataResponse<NodeTemplate[]> {

View File

@@ -21,6 +21,11 @@ export interface BehaviorTree {
graph: NodeGraph
}
export interface BehaviorTreeRequest extends BehaviorTree {
pageNum: number,
pageSize: number,
}
export interface BehaviorTreeDetailsResponse extends ApiDataResponse<BehaviorTree> {

View File

@@ -8,22 +8,23 @@
*/
import type { NodeTemplate } from '../types';
import type { SettingTaskNodeElement, TaskNodeRect } from '../builder/element';
import type { GraphTaskElement, GraphTaskRect } from '../builder/element';
import { generateKey } from '@/utils/strings';
export const createTaskNodeElementFromTemplate = (
export const createGraphTaskElementFromTemplate = (
template: NodeTemplate,
rect?: TaskNodeRect,
): SettingTaskNodeElement => {
let realRect = { width: 200, height: 100, x: 0, y: 0, ...rect || {} }
console.info('rect',rect)
rect?: GraphTaskRect,
): GraphTaskElement => {
let realRect = { width: 120, height: 80, x: 0, y: 0, ...rect || {} };
console.info('rect', rect);
return {
id: 0,
key: generateKey(template.type),
status: null,
type: 'task',
template: template.id,
type: template.type,
templateType: template.templateType,
name: template.name,
category: template.type,
description: template.description,
position: {
x: realRect.x ?? 0,
@@ -31,25 +32,9 @@ export const createTaskNodeElementFromTemplate = (
},
width: realRect.width,
height: realRect.height,
// settings: JSON.parse(JSON.stringify(template.parameter_defs ?? [])),
inputs: null,
outputs: null,
parameters: {},
variables: [
{
key: generateKey('var_'),
name: '范围',
value: '1000',
defaults: '1000',
unit: 'KM'
},
{
key: generateKey('var_'),
name: '武器名称',
value: '地对空导弹',
defaults: '地对空导弹',
unit: '个'
}
],
} as SettingTaskNodeElement;
parameters: template.parameters ?? [],
variables: [],
} as GraphTaskElement;
};

View File

@@ -13,10 +13,9 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AAvatar: typeof import('ant-design-vue/es')['Avatar']
ABadge: typeof import('ant-design-vue/es')['Badge']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
ACarousel: typeof import('ant-design-vue/es')['Carousel']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACol: typeof import('ant-design-vue/es')['Col']
ACollapse: typeof import('ant-design-vue/es')['Collapse']
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
@@ -29,7 +28,6 @@ declare module 'vue' {
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest']
AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
@@ -39,18 +37,15 @@ declare module 'vue' {
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
APopover: typeof import('ant-design-vue/es')['Popover']
AProgress: typeof import('ant-design-vue/es')['Progress']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASlider: typeof import('ant-design-vue/es')['Slider']
ASpace: typeof import('ant-design-vue/es')['Space']
ASteps: typeof import('ant-design-vue/es')['Steps']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ATable: typeof import('ant-design-vue/es')['Table']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
@@ -65,10 +60,9 @@ declare module 'vue' {
// For TSX support
declare global {
const AAvatar: typeof import('ant-design-vue/es')['Avatar']
const ABadge: typeof import('ant-design-vue/es')['Badge']
const AButton: typeof import('ant-design-vue/es')['Button']
const ACard: typeof import('ant-design-vue/es')['Card']
const ACarousel: typeof import('ant-design-vue/es')['Carousel']
const ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
const ACol: typeof import('ant-design-vue/es')['Col']
const ACollapse: typeof import('ant-design-vue/es')['Collapse']
const ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
@@ -81,7 +75,6 @@ declare global {
const AFormItem: typeof import('ant-design-vue/es')['FormItem']
const AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest']
const AInput: typeof import('ant-design-vue/es')['Input']
const AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
const AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
const AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
const AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
@@ -91,18 +84,15 @@ declare global {
const ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
const AList: typeof import('ant-design-vue/es')['List']
const AListItem: typeof import('ant-design-vue/es')['ListItem']
const AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
const AMenu: typeof import('ant-design-vue/es')['Menu']
const AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
const AModal: typeof import('ant-design-vue/es')['Modal']
const APagination: typeof import('ant-design-vue/es')['Pagination']
const APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
const APopover: typeof import('ant-design-vue/es')['Popover']
const AProgress: typeof import('ant-design-vue/es')['Progress']
const ARow: typeof import('ant-design-vue/es')['Row']
const ASelect: typeof import('ant-design-vue/es')['Select']
const ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
const ASlider: typeof import('ant-design-vue/es')['Slider']
const ASpace: typeof import('ant-design-vue/es')['Space']
const ASteps: typeof import('ant-design-vue/es')['Steps']
const ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
const ATable: typeof import('ant-design-vue/es')['Table']
const ATabPane: typeof import('ant-design-vue/es')['TabPane']