Compare commits
99 Commits
e82455a220
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b48c17821d | |||
| 487fffc2d2 | |||
| 1e7e639ee0 | |||
| 4f71fc815b | |||
| 1cde930bb1 | |||
| c9125c8902 | |||
| 59bd191810 | |||
| 7a16b0643e | |||
| 2509b30382 | |||
| ec716df946 | |||
| abe0315536 | |||
| 4252b3f514 | |||
| c597c3c01f | |||
| 4933c2c14e | |||
| 3d0ba1d77c | |||
| 0cd8455c9d | |||
| ced438c725 | |||
| 9bea71e414 | |||
| 2ea04b98f8 | |||
| 8afd87b690 | |||
| 71bb45f6a0 | |||
| 931804555f | |||
| ae01a2aa01 | |||
| c3a6661d2a | |||
| 1de4f9db8d | |||
| 65d99bb7a8 | |||
| 39b04d8b73 | |||
| 4d5f966b0a | |||
| 0cd142bb82 | |||
| bcaaf1da4f | |||
| d310c5ae7b | |||
| 1e38170420 | |||
| 4330be8d0e | |||
| 28cd9c131b | |||
| 27a5dc8e02 | |||
| 243a42ca5c | |||
| b56d57af44 | |||
| 012939829c | |||
| 82027fc6aa | |||
| 6610e06991 | |||
| 40655dd557 | |||
| 5079f17df0 | |||
| 913dea7afa | |||
| 6f48a06438 | |||
| 6941954e14 | |||
| 2c834b9b2f | |||
| 447aef0fe3 | |||
| 873e31970f | |||
| b39a85a074 | |||
| d8c3429163 | |||
| dbbc8a27c3 | |||
| 8e96285c0a | |||
| 559a550157 | |||
| a08e061979 | |||
| 8975777cd5 | |||
| f51619526a | |||
| 0691fddbf1 | |||
| 2d60a8b90f | |||
| e92423ee54 | |||
| 99dfb6f009 | |||
| 4404d0e411 | |||
| a67e3e42ba | |||
| 44ff489c08 | |||
| 3c06e77c6d | |||
| e555c95058 | |||
| 8839c65d7f | |||
| f9eb10c783 | |||
| 949e059c8f | |||
| 9858787570 | |||
| 5a8c707340 | |||
| e1fe2cf7da | |||
| 3ae6a693e1 | |||
| 178bceabbb | |||
| 6969fe5744 | |||
| d43effe612 | |||
| f66d707520 | |||
| 2b2a11831d | |||
| a10b16a0ff | |||
| 85b4a7dd2f | |||
| a277fe22a6 | |||
| 836e42625e | |||
| 69c01bc62b | |||
| bd62efca3f | |||
| ee019d3c0f | |||
| 80fa2de819 | |||
| 94b15ef412 | |||
| 4e28782d59 | |||
| 615eac3bfe | |||
| e24920acf1 | |||
| 1c562c134c | |||
| c6c38332d9 | |||
| f51b27dc7c | |||
| a6c6cf2427 | |||
| 373dc390fa | |||
| 2ed1129fe4 | |||
| ce4c23eff8 | |||
| 9f4ef1ab94 | |||
| c800811bed | |||
| 235fd9b6e1 |
14
.gitignore
vendored
14
.gitignore
vendored
@@ -51,3 +51,17 @@ nbdist/
|
||||
!*/build/*.java
|
||||
!*/build/*.html
|
||||
!*/build/*.xml
|
||||
modeler/pnpm-lock.yaml
|
||||
|
||||
# Application configuration files (may contain sensitive info)
|
||||
**/application.yml
|
||||
**/application-*.yml
|
||||
!**/application-example.yml
|
||||
|
||||
######################################################################
|
||||
# Mock bundled cache files
|
||||
**/*.bundled_*.mjs
|
||||
**/__*.bundled_*.mjs
|
||||
**/___*.bundled_*.mjs
|
||||
modeler/types/components.d.ts
|
||||
modeler/types/auto-imports.d.ts
|
||||
|
||||
@@ -81,6 +81,21 @@
|
||||
<artifactId>solution-scene</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 单元测试依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security 测试支持(如果接口有权限控制) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -3,19 +3,14 @@ package com.solution.web.controller.behaviour;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.solution.system.domain.PlatFormDTO;
|
||||
import com.solution.web.core.BehaviortreeProcessor;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.solution.common.annotation.Log;
|
||||
import com.solution.common.core.controller.BaseController;
|
||||
import com.solution.common.core.domain.AjaxResult;
|
||||
@@ -135,17 +130,36 @@ public class BehaviortreeController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据行为树id获取该行为树的下属
|
||||
* @param treeId
|
||||
* 根据平台id获取该行为树的下属
|
||||
* @param platformId
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation("根据行为树id获取该行为树的下属")
|
||||
@ApiOperation("根据平台id获取该行为树的下属")
|
||||
@PreAuthorize("@ss.hasPermi('system:behaviortree:query')")
|
||||
@GetMapping("/underling/{treeId}")
|
||||
public AjaxResult getUnderling(@PathVariable Integer treeId)
|
||||
@GetMapping("/underling/{platformId}")
|
||||
public AjaxResult getUnderling(@PathVariable Integer platformId)
|
||||
{
|
||||
return success(behaviortreeService.getUnderling(treeId));
|
||||
return success(behaviortreeService.getUnderling(platformId));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据场景id获取总指挥的行为树的原数据
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation("根据场景id获取总指挥的行为树的原数据")
|
||||
@GetMapping("/commander/{scenarioId}")
|
||||
public AjaxResult getCommander(@PathVariable Integer scenarioId){
|
||||
return success(behaviortreeService.getCommander(scenarioId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改平台关联的行为树id
|
||||
* @param platFormDTO
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation("修改平台关联的行为树id")
|
||||
@PutMapping("/behaviortreeId")
|
||||
public AjaxResult updateBehaviortreeId(@RequestBody PlatFormDTO platFormDTO){
|
||||
return toAjax(behaviortreeService.updateBehaviortreeId(platFormDTO));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,25 @@ import com.solution.common.core.controller.BaseController;
|
||||
import com.solution.common.core.domain.AjaxResult;
|
||||
import com.solution.common.core.page.TableDataInfo;
|
||||
import com.solution.common.enums.BusinessType;
|
||||
import com.solution.common.utils.poi.ExcelUtil;
|
||||
import com.solution.rule.domain.Rule;
|
||||
import com.solution.rule.domain.config.RuleConfig;
|
||||
import com.solution.rule.domain.config.RuleConfigExcelRow;
|
||||
import com.solution.rule.domain.config.RuleConfigQuery;
|
||||
import com.solution.rule.domain.config.RuleParamMeta;
|
||||
import com.solution.rule.domain.config.vo.RuleDecisionTreeVO;
|
||||
import com.solution.rule.domain.config.vo.RuleFourBlocksGraphVO;
|
||||
import com.solution.rule.domain.config.vo.RuleGraphVO;
|
||||
import com.solution.rule.service.IRuleService;
|
||||
import com.solution.rule.service.IRuleConfigService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@Api("红蓝对抗规则管理")
|
||||
@@ -22,8 +33,10 @@ public class RuleController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IRuleService ruleService;
|
||||
@Autowired
|
||||
private IRuleConfigService ruleConfigService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:list')")
|
||||
/*@PreAuthorize("@ss.hasPermi('system:rule:list')")
|
||||
@GetMapping("/list")
|
||||
@ApiOperation("查询规则列表")
|
||||
public TableDataInfo list(Rule rule) {
|
||||
@@ -61,5 +74,109 @@ public class RuleController extends BaseController {
|
||||
@ApiOperation("删除规则")
|
||||
public AjaxResult remove(@PathVariable Integer[] ids) {
|
||||
return toAjax(ruleService.deleteRuleByIds(ids));
|
||||
}*/
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:list')")
|
||||
@GetMapping("/config/list")
|
||||
@ApiOperation("查询规则聚合列表")
|
||||
public TableDataInfo configList(RuleConfigQuery query) {
|
||||
startPage();
|
||||
return getDataTable(ruleConfigService.selectRuleConfigList(query));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:list')")
|
||||
@GetMapping("/config/graph")
|
||||
@ApiOperation("规则知识图谱(与列表相同的分页与筛选条件)")
|
||||
public AjaxResult configGraph(RuleConfigQuery query) {
|
||||
startPage();
|
||||
List<RuleConfig> list = ruleConfigService.selectRuleConfigList(query);
|
||||
RuleGraphVO graph = ruleConfigService.buildKnowledgeGraph(list);
|
||||
return success(graph);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:list')")
|
||||
@GetMapping("/config/decision-tree")
|
||||
@ApiOperation("规则决策树(围绕装备/目标/阵位/航迹四类规则,展示参数进入后的决策路径)")
|
||||
public AjaxResult configDecisionTree(RuleConfigQuery query) {
|
||||
startPage();
|
||||
List<RuleConfig> list = ruleConfigService.selectRuleConfigList(query);
|
||||
RuleDecisionTreeVO tree = ruleConfigService.buildDecisionTree(list);
|
||||
return success(tree);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:list')")
|
||||
@GetMapping("/config/graph/four-blocks")
|
||||
@ApiOperation("四块规则知识图谱(装备/目标/阵位/航迹;参数值与运行时 globalParams 一致)")
|
||||
public AjaxResult configGraphFourBlocks() {
|
||||
RuleFourBlocksGraphVO vo = ruleConfigService.buildFourBlocksKnowledgeGraph();
|
||||
return success(vo);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:query')")
|
||||
@GetMapping("/config/{ruleCode}")
|
||||
@ApiOperation("查询规则聚合详情")
|
||||
public AjaxResult configInfo(@PathVariable String ruleCode) {
|
||||
return success(ruleConfigService.selectRuleConfigByCode(ruleCode));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:add')")
|
||||
@Log(title = "规则聚合管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/config")
|
||||
@ApiOperation("新增规则聚合")
|
||||
public AjaxResult addConfig(@RequestBody RuleConfig ruleConfig) {
|
||||
return toAjax(ruleConfigService.insertRuleConfig(ruleConfig));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:edit')")
|
||||
@Log(title = "规则聚合管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/config")
|
||||
@ApiOperation("修改规则聚合")
|
||||
public AjaxResult editConfig(@RequestBody RuleConfig ruleConfig) {
|
||||
return toAjax(ruleConfigService.updateRuleConfig(ruleConfig));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:remove')")
|
||||
@Log(title = "规则聚合管理", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/config/{ruleCodes}")
|
||||
@ApiOperation("删除规则聚合")
|
||||
public AjaxResult removeConfig(@PathVariable String[] ruleCodes) {
|
||||
return toAjax(ruleConfigService.deleteRuleConfigByCodes(ruleCodes));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:query')")
|
||||
@GetMapping("/config/dict/{dictType}")
|
||||
@ApiOperation("按类型查询规则字典")
|
||||
public AjaxResult dict(@PathVariable String dictType) {
|
||||
return success(ruleConfigService.selectDictByType(dictType));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:query')")
|
||||
@GetMapping("/config/param-meta")
|
||||
@ApiOperation("查询参数元数据")
|
||||
public AjaxResult paramMeta() {
|
||||
List<RuleParamMeta> metas = ruleConfigService.selectParamMetaList();
|
||||
return success(metas);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:query')")
|
||||
@Log(title = "Rule Config", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/config/export")
|
||||
@ApiOperation("Export rule config")
|
||||
public void exportConfig(HttpServletResponse response, @RequestBody(required = false) RuleConfigQuery query) {
|
||||
List<RuleConfigExcelRow> rows = ruleConfigService.exportRuleConfigRows(query == null ? new RuleConfigQuery() : query);
|
||||
ExcelUtil<RuleConfigExcelRow> util = new ExcelUtil<>(RuleConfigExcelRow.class);
|
||||
util.exportExcel(response, rows, "rule-config");
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('system:rule:edit')")
|
||||
@Log(title = "Rule Config", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/config/importData")
|
||||
@ApiOperation("Import rule config")
|
||||
public AjaxResult importConfig(MultipartFile file,
|
||||
@RequestParam(value = "updateSupport", defaultValue = "true") boolean updateSupport) throws Exception {
|
||||
ExcelUtil<RuleConfigExcelRow> util = new ExcelUtil<>(RuleConfigExcelRow.class);
|
||||
List<RuleConfigExcelRow> rows = util.importExcel(file.getInputStream());
|
||||
String result = ruleConfigService.importRuleConfigRows(rows, updateSupport);
|
||||
return success(result);
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,28 @@ public class BehaviortreeProcessor {
|
||||
String newEnglishName = englishName + "_" + behaviortree.getId();
|
||||
behaviortree.setEnglishName(newEnglishName);
|
||||
behaviortree.setName(newName);
|
||||
//不做前置判断,必走数据库count
|
||||
//获取行为树重复名称个数
|
||||
Integer num = behaviortreeService.getCountName(newName);
|
||||
if(num > 0){
|
||||
// 从2开始尝试,因为基础名称已经存在
|
||||
int count = 2;
|
||||
String finalName;
|
||||
String finalEnglishName;
|
||||
do {
|
||||
finalName = newName + "_" + count;
|
||||
finalEnglishName = newEnglishName + "_" + count;
|
||||
// 检查当前生成的名称是否存在
|
||||
num = behaviortreeService.getCountName(finalName);
|
||||
count++;
|
||||
} while(num > 0);
|
||||
|
||||
behaviortree.setName(finalName);
|
||||
behaviortree.setEnglishName(finalEnglishName);
|
||||
} else {
|
||||
behaviortree.setName(newName);
|
||||
behaviortree.setEnglishName(newEnglishName);
|
||||
}
|
||||
return this.create(behaviortree);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ spring:
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
# url: jdbc:mysql://192.168.166.71:3306/behaviortreedb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://localhost:3306/autosolution_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://127.0.0.1:3306/autosolution_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
# password: 123456
|
||||
password: 1234
|
||||
# 从库数据源
|
||||
slave:
|
||||
|
||||
@@ -32,7 +32,7 @@ public class Nodeparameter extends BaseEntity
|
||||
@Excel(name = "节点实例设置的具体参数值 (覆盖模板默认值)")
|
||||
private String value;
|
||||
|
||||
private int groupIndex;
|
||||
private Integer groupIndex;
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
@@ -48,6 +48,14 @@ public class Nodeparameter extends BaseEntity
|
||||
return treeId;
|
||||
}
|
||||
|
||||
public Integer getGroupIndex() {
|
||||
return groupIndex;
|
||||
}
|
||||
|
||||
public void setGroupIndex(Integer groupIndex) {
|
||||
this.groupIndex = groupIndex;
|
||||
}
|
||||
|
||||
public void setTreeId(Long treeId) {
|
||||
this.treeId = treeId;
|
||||
}
|
||||
@@ -82,13 +90,6 @@ public class Nodeparameter extends BaseEntity
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getGroupIndex() {
|
||||
return groupIndex;
|
||||
}
|
||||
|
||||
public void setGroupIndex(int groupIndex) {
|
||||
this.groupIndex = groupIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.solution.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PlatFormDTO {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Integer behaviortreeId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.solution.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 收集数据用来生成总指挥官
|
||||
*/
|
||||
@Data
|
||||
public class PlatformChiefCommander {
|
||||
|
||||
/**
|
||||
* Primary Key 主键ID(自增)
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* Create Time 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 时间
|
||||
*/
|
||||
private Integer time;
|
||||
|
||||
/**
|
||||
* 指令
|
||||
*/
|
||||
private String command;
|
||||
|
||||
/**
|
||||
* 下属指挥官
|
||||
*/
|
||||
private String subordinateCommander;
|
||||
|
||||
/**
|
||||
* 场景ID
|
||||
*/
|
||||
private Integer scenarioId;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.solution.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 平台视图对象 platform
|
||||
*/
|
||||
@Data
|
||||
public class PlatformVO
|
||||
{
|
||||
/** 主键 */
|
||||
private Integer id;
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 描述 */
|
||||
private String description;
|
||||
|
||||
/** 平台出现在想定上的想定id */
|
||||
private Integer scenarioId;
|
||||
|
||||
/** 经度 */
|
||||
private String longitude;
|
||||
|
||||
/** 纬度 */
|
||||
private String latitude;
|
||||
|
||||
/** 平台类型 */
|
||||
private String platformType;
|
||||
|
||||
/** 海拔 */
|
||||
private Float altitude;
|
||||
}
|
||||
@@ -39,11 +39,11 @@ public class Treenodeinstance extends BaseEntity
|
||||
private Long preconditionTempleteId;
|
||||
|
||||
@Excel(name = "节点介绍")
|
||||
private String desciption;
|
||||
private String nodeValue;
|
||||
|
||||
/** $column.columnComment */
|
||||
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
|
||||
private String uuid;
|
||||
@Excel(name = "是否外部节点", readConverterExp = "0=否,1=是")
|
||||
private String isExtern;
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
@@ -105,36 +105,33 @@ public class Treenodeinstance extends BaseEntity
|
||||
return preconditionTempleteId;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
public String getNodeValue() {
|
||||
return nodeValue;
|
||||
}
|
||||
|
||||
public String getUuid()
|
||||
{
|
||||
return uuid;
|
||||
public void setNodeValue(String nodeValue) {
|
||||
this.nodeValue = nodeValue;
|
||||
}
|
||||
|
||||
public String getDesciption() {
|
||||
return desciption;
|
||||
public String getIsExtern() {
|
||||
return isExtern;
|
||||
}
|
||||
|
||||
public void setDesciption(String desciption) {
|
||||
this.desciption = desciption;
|
||||
public void setIsExtern(String isExtern) {
|
||||
this.isExtern = isExtern;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("treeId", getTreeId())
|
||||
.append("templateId", getTemplateId())
|
||||
.append("instanceName", getInstanceName())
|
||||
.append("isRoot", getIsRoot())
|
||||
.append("preconditionTempleteId", getPreconditionTempleteId())
|
||||
.append("uuid", getUuid())
|
||||
.append("desciption", getDesciption())
|
||||
.toString();
|
||||
return "Treenodeinstance{" +
|
||||
"id=" + id +
|
||||
", treeId=" + treeId +
|
||||
", templateId=" + templateId +
|
||||
", instanceName='" + instanceName + '\'' +
|
||||
", isRoot=" + isRoot +
|
||||
", preconditionTempleteId=" + preconditionTempleteId +
|
||||
", nodeValue='" + nodeValue + '\'' +
|
||||
", isExtern='" + isExtern + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.solution.system.mapper;
|
||||
import java.util.List;
|
||||
|
||||
import com.solution.system.domain.Behaviortree;
|
||||
import com.solution.system.domain.PlatformChiefCommander;
|
||||
|
||||
/**
|
||||
* 行为树主Mapper接口
|
||||
@@ -62,4 +63,18 @@ public interface BehaviortreeMapper
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteBehaviortreeByIds(Long[] ids);
|
||||
|
||||
/**
|
||||
* 根据场景id获取总指挥的行为树的原数据
|
||||
* @param scenarioId
|
||||
* @return
|
||||
*/
|
||||
List<PlatformChiefCommander> getCommander(Integer scenarioId);
|
||||
|
||||
/**
|
||||
* 获取行为树重复名称个数
|
||||
* @param newName
|
||||
* @return
|
||||
*/
|
||||
Integer getCountName(String newName);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.solution.system.mapper;
|
||||
|
||||
import com.solution.system.domain.PlatFormDTO;
|
||||
import com.solution.system.domain.PlatformTree;
|
||||
import com.solution.system.domain.PlatformVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@@ -9,12 +11,7 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface PlatformMapper {
|
||||
|
||||
/**
|
||||
* 根据行为树id获取行为树所属平台
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
PlatformTree getPlatformByTreeId(Integer id);
|
||||
|
||||
|
||||
/**
|
||||
* 根据下属平台英文名获取中文名返回
|
||||
@@ -22,4 +19,25 @@ public interface PlatformMapper {
|
||||
* @return
|
||||
*/
|
||||
List<String> selectPlatformChineseName(@Param("underlingEnglishName") List<String> underlingEnglishName);
|
||||
|
||||
/**
|
||||
* 根据平台id获取平台实体
|
||||
* @param platformId
|
||||
* @return
|
||||
*/
|
||||
PlatformTree getPlatformById(Integer platformId);
|
||||
|
||||
/**
|
||||
* 根据平台英文名查询该平台实体
|
||||
* @param underlingEnglishName
|
||||
* @return
|
||||
*/
|
||||
List<PlatformVO> getPlatformByEnglishName(@Param("underlingEnglishName") List<String> underlingEnglishName);
|
||||
|
||||
/**
|
||||
* 修改平台关联的行为树id
|
||||
* @param platFormDTO
|
||||
* @return
|
||||
*/
|
||||
Integer updateBehaviortreeId(PlatFormDTO platFormDTO);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package com.solution.system.service;
|
||||
import java.util.List;
|
||||
|
||||
import com.solution.system.domain.Behaviortree;
|
||||
import com.solution.system.domain.PlatFormDTO;
|
||||
import com.solution.system.domain.PlatformChiefCommander;
|
||||
import com.solution.system.domain.PlatformVO;
|
||||
|
||||
/**
|
||||
* 行为树主Service接口
|
||||
@@ -33,7 +36,6 @@ public interface IBehaviortreeService
|
||||
|
||||
/**
|
||||
* 新增行为树主
|
||||
*
|
||||
* @param behaviortree 行为树主
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -64,9 +66,30 @@ public interface IBehaviortreeService
|
||||
public int deleteBehaviortreeById(Long id);
|
||||
|
||||
/**
|
||||
* 根据行为树id获取该行为树的下属
|
||||
* @param treeId
|
||||
* 根据平台id获取该行为树的下属
|
||||
* @param platformId
|
||||
* @return
|
||||
*/
|
||||
List<String> getUnderling(Integer treeId);
|
||||
List<PlatformVO> getUnderling(Integer platformId);
|
||||
|
||||
/**
|
||||
* 根据场景id获取总指挥的行为树的原数据
|
||||
* @param scenarioId
|
||||
* @return
|
||||
*/
|
||||
List<PlatformChiefCommander> getCommander(Integer scenarioId);
|
||||
|
||||
/**
|
||||
* 获取行为树重复名称个数
|
||||
* @param newName
|
||||
* @return
|
||||
*/
|
||||
Integer getCountName(String newName);
|
||||
|
||||
/**
|
||||
* 修改平台关联的行为树id
|
||||
* @param platFormDTO
|
||||
* @return
|
||||
*/
|
||||
Integer updateBehaviortreeId(PlatFormDTO platFormDTO);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import java.util.List;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.solution.common.constant.ExceptionConstants;
|
||||
import com.solution.system.domain.PlatformTree;
|
||||
import com.solution.system.domain.*;
|
||||
import com.solution.system.mapper.PlatformCommunicationMapper;
|
||||
import com.solution.system.mapper.PlatformMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.solution.system.mapper.BehaviortreeMapper;
|
||||
import com.solution.system.domain.Behaviortree;
|
||||
import com.solution.system.service.IBehaviortreeService;
|
||||
|
||||
/**
|
||||
@@ -111,28 +110,60 @@ public class BehaviortreeServiceImpl implements IBehaviortreeService
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据行为树id获取该行为树的下属
|
||||
* @param treeId
|
||||
* 根据平台id获取该行为树的下属
|
||||
* @param platformId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<String> getUnderling(Integer treeId) {
|
||||
if(null == treeId){
|
||||
public List<PlatformVO> getUnderling(Integer platformId) {
|
||||
if(null == platformId){
|
||||
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
|
||||
}
|
||||
Behaviortree behaviortree = behaviortreeMapper.selectBehaviortreeById(Long.valueOf(treeId));
|
||||
if(ObjectUtil.isEmpty(behaviortree) || null == behaviortree.getId()){
|
||||
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
|
||||
}
|
||||
//根据行为树id获取行为树所属平台
|
||||
PlatformTree platform = platformMapper.getPlatformByTreeId(behaviortree.getPlatformId());
|
||||
//根据平台id获取平台实体
|
||||
PlatformTree platform = platformMapper.getPlatformById(platformId);
|
||||
//根据平台name获取平台下属英文名
|
||||
List<String> underlingEnglishName = platformCommunicationMapper.getUnderlingBytreeId(platform.getName());
|
||||
//根据下属平台英文名获取中文名返回
|
||||
List<String> underlingChineseName = platformMapper.selectPlatformChineseName(underlingEnglishName);
|
||||
if(CollUtil.isEmpty(underlingChineseName)){
|
||||
if(CollUtil.isEmpty(underlingEnglishName)){
|
||||
throw new RuntimeException("该平台暂无下属");
|
||||
}
|
||||
return underlingChineseName;
|
||||
//根据平台英文名查询该平台实体
|
||||
List<PlatformVO> resultList = platformMapper.getPlatformByEnglishName(underlingEnglishName);
|
||||
if(CollUtil.isEmpty(resultList)){
|
||||
throw new RuntimeException("无法根据平台英文名获取平台实体");
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据场景id获取总指挥的行为树的原数据
|
||||
* @param scenarioId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<PlatformChiefCommander> getCommander(Integer scenarioId) {
|
||||
return behaviortreeMapper.getCommander(scenarioId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取行为树重复名称个数
|
||||
* @param newName
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Integer getCountName(String newName) {
|
||||
return behaviortreeMapper.getCountName(newName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改平台关联的行为树id
|
||||
* @param platFormDTO
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Integer updateBehaviortreeId(PlatFormDTO platFormDTO) {
|
||||
if(ObjectUtil.isEmpty(platFormDTO)){
|
||||
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
|
||||
}
|
||||
return platformMapper.updateBehaviortreeId(platFormDTO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</select>
|
||||
|
||||
<sql id="selectBehaviortreeVo">
|
||||
select id, name, description, created_at, updated_at, english_name, xml_content from behaviortree
|
||||
select id, name, description, created_at, updated_at, english_name, xml_content, platform_id, scenario_id from behaviortree
|
||||
</sql>
|
||||
|
||||
|
||||
<select id="selectBehaviortreeList" parameterType="Behaviortree" resultMap="BehaviortreeResult">
|
||||
<include refid="selectBehaviortreeVo"/>
|
||||
<where>
|
||||
@@ -35,6 +36,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updatedAt != null "> and updated_at = #{updatedAt}</if>
|
||||
<if test="englishName != null and englishName != ''"> and english_name like concat('%', #{englishName}, '%')</if>
|
||||
<if test="xmlContent != null and xmlContent != ''"> and xml_content = #{xmlContent}</if>
|
||||
<if test="xmlContent != null and xmlContent != ''"> and xml_content = #{xmlContent}</if>
|
||||
<if test="platformId != null">platform_id = #{platformId}</if>
|
||||
<if test="scenarioId != null">scenario_id = #{scenarioId}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -42,6 +46,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select id, name, description, created_at, updated_at, english_name,graph, xml_content ,platform_id, scenario_id from behaviortree
|
||||
where id = #{id}
|
||||
</select>
|
||||
<select id="getCommander" resultType="com.solution.system.domain.PlatformChiefCommander"
|
||||
parameterType="java.lang.Integer">
|
||||
SELECT id,
|
||||
create_time AS createTime,
|
||||
name,time,command,
|
||||
subordinate_commander AS subordinateCommander,
|
||||
scenario_id AS scenarioId
|
||||
FROM platform_chief_commander
|
||||
WHERE scenario_id = #{scenarioId}
|
||||
</select>
|
||||
<select id="getCountName" resultType="java.lang.Integer" parameterType="java.lang.String">
|
||||
SELECT count(*)
|
||||
FROM behaviortree
|
||||
WHERE name = #{name}
|
||||
</select>
|
||||
|
||||
<insert id="insertBehaviortree" parameterType="Behaviortree" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into behaviortree
|
||||
@@ -53,6 +72,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="englishName != null and englishName != ''">english_name,</if>
|
||||
<if test="xmlContent != null">xml_content,</if>
|
||||
<if test="platformId != null">platform_id,</if>
|
||||
<if test="scenarioId != null">scenario_id,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="name != null and name != ''">#{name},</if>
|
||||
@@ -62,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="englishName != null and englishName != ''">#{englishName},</if>
|
||||
<if test="xmlContent != null">#{xmlContent},</if>
|
||||
<if test="platformId != null">#{platformId},</if>
|
||||
<if test="scenarioId != null">#{scenarioId},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
|
||||
@@ -18,15 +18,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</delete>
|
||||
|
||||
<sql id="selectNodeparameterVo">
|
||||
select id, treeId, node_instance_id, param_def_id,`value`, group_index from nodeparameter
|
||||
select id, tree_id, node_instance_id, param_def_id,`value`, group_index from nodeparameter
|
||||
</sql>
|
||||
|
||||
<select id="selectNodeparameterList" parameterType="Nodeparameter" resultMap="NodeparameterResult">
|
||||
<include refid="selectNodeparameterVo"/>
|
||||
<where>
|
||||
<if test="id != null"> and id = #{id}</if>
|
||||
<if test="treeId != null"> and tree_id = #{treeId}</if>
|
||||
<if test="nodeInstanceId != null "> and node_instance_id = #{nodeInstanceId}</if>
|
||||
<if test="paramDefId != null "> and param_def_id = #{paramDefId}</if>
|
||||
<if test="value != null and value != ''"> and value = #{value}</if>
|
||||
<if test="value != null and value != ''"> and `value` = #{value}</if>
|
||||
<if test="groupIndex != null"> and group_index = #{groupIndex}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<mapper namespace="com.solution.system.mapper.PlatformCommunicationMapper">
|
||||
|
||||
|
||||
<select id="getUnderlingBytreeId" resultType="java.lang.String" parameterType="java.lang.Integer">
|
||||
<select id="getUnderlingBytreeId" resultType="java.lang.String" parameterType="java.lang.String">
|
||||
SELECT subordinate_platform
|
||||
FROM platform_communication
|
||||
WHERE subordinate_platform = #{name}
|
||||
WHERE command_platform = #{name}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -3,14 +3,12 @@
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.solution.system.mapper.PlatformMapper">
|
||||
|
||||
|
||||
<select id="getPlatformByTreeId" resultType="com.solution.system.domain.PlatformTree"
|
||||
parameterType="java.lang.Integer">
|
||||
SELECT id , name , description, scenario_id
|
||||
FROM platform
|
||||
<update id="updateBehaviortreeId">
|
||||
UPDATE platform
|
||||
SET behaviortree_id = #{behaviortreeId}
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
</update>
|
||||
|
||||
<select id="selectPlatformChineseName" resultType="java.lang.String"
|
||||
parameterType="java.util.List">
|
||||
SELECT description
|
||||
@@ -20,4 +18,22 @@
|
||||
#{item}
|
||||
</foreach>
|
||||
</select>
|
||||
<select id="getPlatformById" resultType="com.solution.system.domain.PlatformTree"
|
||||
parameterType="java.lang.Integer">
|
||||
SELECT id, name, description, scenario_id AS scenarioId
|
||||
FROM platform
|
||||
WHERE id = #{platformId}
|
||||
</select>
|
||||
<select id="getPlatformByEnglishName" resultType="com.solution.system.domain.PlatformVO">
|
||||
SELECT id,name,description,
|
||||
scenario_id AS scenarioId,
|
||||
longitude,latitude,
|
||||
platform_type AS platformType,
|
||||
altitude
|
||||
FROM platform
|
||||
WHERE name IN
|
||||
<foreach item="item" collection="underlingEnglishName" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -21,6 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="selectTemplateparameterdefList" parameterType="templateparameterdef" resultMap="TemplateparameterdefResult">
|
||||
<include refid="selectTemplateparameterdefVo"/>
|
||||
<where>
|
||||
<if test="id != null"> and id = #{id}</if>
|
||||
<if test="templateId != null "> and template_id = #{templateId}</if>
|
||||
<if test="paramKey != null and paramKey != ''"> and param_key = #{paramKey}</if>
|
||||
<if test="dataType != null and dataType != ''"> and data_type = #{dataType}</if>
|
||||
|
||||
@@ -11,8 +11,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="instanceName" column="instance_name" />
|
||||
<result property="isRoot" column="is_root" />
|
||||
<result property="preconditionTempleteId" column="precondition_templete_id" />
|
||||
<result property="uuid" column="uuid" />
|
||||
<result property="desciption" column="desciption" />
|
||||
<result property="nodeValue" column="node_value" />
|
||||
<result property="isExtern" column="is_extern" />
|
||||
</resultMap>
|
||||
|
||||
<delete id="deleteByTreeId">
|
||||
@@ -20,7 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</delete>
|
||||
|
||||
<sql id="selectTreenodeinstanceVo">
|
||||
select id, tree_id, template_id, instance_name, is_root, precondition_templete_id, uuid,desciption from treenodeinstance
|
||||
select id, tree_id, template_id, instance_name, is_root, precondition_templete_id, node_value,is_extern from treenodeinstance
|
||||
</sql>
|
||||
|
||||
<select id="selectTreenodeinstanceList" parameterType="Treenodeinstance" resultMap="TreenodeinstanceResult">
|
||||
@@ -31,8 +31,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="instanceName != null and instanceName != ''"> and instance_name like concat('%', #{instanceName}, '%')</if>
|
||||
<if test="isRoot != null "> and is_root = #{isRoot}</if>
|
||||
<if test="preconditionTempleteId != null "> and precondition_templete_id = #{preconditionTempleteId}</if>
|
||||
<if test="uuid != null and uuid != ''"> and uuid = #{uuid}</if>
|
||||
<if test="desciption != null and desciption != ''"> and desciption = #{desciption}</if>
|
||||
<if test="nodeValue != null and nodeValue != ''"> and node_value = #{nodeValue}</if>
|
||||
<if test="isExtern != null and isExtern != ''"> and is_extern = #{isExtern}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -49,8 +49,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="instanceName != null">instance_name,</if>
|
||||
<if test="isRoot != null">is_root,</if>
|
||||
<if test="preconditionTempleteId != null">precondition_templete_id,</if>
|
||||
<if test="uuid != null">uuid,</if>
|
||||
<if test="desciption != null">desciption,</if>
|
||||
<if test="nodeValue != null">node_value,</if>
|
||||
<if test="isExtern != null">is_extern,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="treeId != null">#{treeId},</if>
|
||||
@@ -58,8 +58,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="instanceName != null">#{instanceName},</if>
|
||||
<if test="isRoot != null">#{isRoot},</if>
|
||||
<if test="preconditionTempleteId != null">#{preconditionTempleteId},</if>
|
||||
<if test="uuid != null">#{uuid},</if>
|
||||
<if test="desciption != null">#{desciption},</if>
|
||||
<if test="nodeValue != null">#{node_value},</if>
|
||||
<if test="isExtern != null">#{is_extern},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
@@ -71,8 +71,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="instanceName != null">instance_name = #{instanceName},</if>
|
||||
<if test="isRoot != null">is_root = #{isRoot},</if>
|
||||
<if test="preconditionTempleteId != null">precondition_templete_id = #{preconditionTempleteId},</if>
|
||||
<if test="uuid != null">uuid = #{uuid},</if>
|
||||
<if test="desciption != null">desciption = #{desciption},</if>
|
||||
<if test="nodeValue != null">#{node_value},</if>
|
||||
<if test="isExtern != null">#{is_extern},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.solution.rule.controller;
|
||||
|
||||
import com.solution.rule.domain.tasks.Kj3PostResult;
|
||||
import com.solution.rule.service.Kj3TaskDataService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/rule/kj3")
|
||||
public class Kj3TaskDataController {
|
||||
|
||||
@Autowired
|
||||
private Kj3TaskDataService kj3TaskDataService;
|
||||
|
||||
/**
|
||||
* 接收外部传入的 kj-3 JSON,筛选红方任务并转换后 POST 到目标地址。
|
||||
*
|
||||
* @param kj3Json 外部请求体 JSON
|
||||
* @return 处理结果
|
||||
*/
|
||||
@PostMapping("/parse-and-post")
|
||||
public Kj3PostResult parseAndPost(@RequestBody String kj3Json) {
|
||||
return kj3TaskDataService.parseAndPostRedTasks(kj3Json);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,16 @@ public class BasicPlatform implements Serializable {
|
||||
|
||||
private String description;
|
||||
|
||||
private Integer behaviortreeId;
|
||||
|
||||
public Integer getBehaviortreeId() {
|
||||
return behaviortreeId;
|
||||
}
|
||||
|
||||
public void setBehaviortreeId(Integer behaviortreeId) {
|
||||
this.behaviortreeId = behaviortreeId;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则聚合对象")
|
||||
public class RuleConfig {
|
||||
|
||||
@ApiModelProperty("主键ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("规则编码")
|
||||
private String ruleCode;
|
||||
|
||||
@ApiModelProperty("规则名称")
|
||||
private String ruleName;
|
||||
|
||||
@ApiModelProperty("层级编码(task/action/platform)")
|
||||
private String levelCode;
|
||||
|
||||
@ApiModelProperty("种类编码(select/assign/deploy/config/mode/spacetime/relation/limit)")
|
||||
private String kindCode;
|
||||
|
||||
@ApiModelProperty("模块编码(equipment/target/position/track/group)")
|
||||
private String moduleCode;
|
||||
|
||||
@ApiModelProperty("优先级(数字越小越先执行)")
|
||||
private Integer priorityNo;
|
||||
|
||||
@ApiModelProperty("条件表达式")
|
||||
private String conditionExpr;
|
||||
|
||||
@ApiModelProperty("动作表达式")
|
||||
private String actionExpr;
|
||||
|
||||
@ApiModelProperty("版本号")
|
||||
private Integer versionNo;
|
||||
|
||||
@ApiModelProperty("是否启用(1是0否)")
|
||||
private Integer enabled;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private Date createdAt;
|
||||
|
||||
@ApiModelProperty("更新时间")
|
||||
private Date updatedAt;
|
||||
|
||||
@ApiModelProperty("参数列表")
|
||||
private List<RuleConfigParam> params;
|
||||
|
||||
@ApiModelProperty("适用任务类型编码列表")
|
||||
private List<String> taskTypes;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import com.solution.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RuleConfigExcelRow {
|
||||
|
||||
@Excel(name = "规则编码")
|
||||
private String ruleCode;
|
||||
|
||||
@Excel(name = "规则名称")
|
||||
private String ruleName;
|
||||
|
||||
@Excel(name = "层级编码")
|
||||
private String levelCode;
|
||||
|
||||
@Excel(name = "类别编码")
|
||||
private String kindCode;
|
||||
|
||||
@Excel(name = "模块编码")
|
||||
private String moduleCode;
|
||||
|
||||
@Excel(name = "优先级", cellType = Excel.ColumnType.NUMERIC)
|
||||
private Integer priorityNo;
|
||||
|
||||
@Excel(name = "条件表达式", width = 24)
|
||||
private String conditionExpr;
|
||||
|
||||
@Excel(name = "动作表达式", width = 24)
|
||||
private String actionExpr;
|
||||
|
||||
@Excel(name = "版本号", cellType = Excel.ColumnType.NUMERIC)
|
||||
private Integer versionNo;
|
||||
|
||||
@Excel(name = "规则启用", cellType = Excel.ColumnType.NUMERIC, prompt = "1=启用,0=停用")
|
||||
private Integer enabled;
|
||||
|
||||
@Excel(name = "备注", width = 20)
|
||||
private String remark;
|
||||
|
||||
@Excel(name = "任务类型列表", width = 20, prompt = "多个任务类型用英文逗号分隔")
|
||||
private String taskTypesCsv;
|
||||
|
||||
@Excel(name = "参数名称")
|
||||
private String paramName;
|
||||
|
||||
@Excel(name = "参数键")
|
||||
private String paramKey;
|
||||
|
||||
@Excel(name = "参数值", width = 24)
|
||||
private String paramVal;
|
||||
|
||||
@Excel(name = "参数值类型")
|
||||
private String valType;
|
||||
|
||||
@Excel(name = "参数排序", cellType = Excel.ColumnType.NUMERIC)
|
||||
private Integer sortNo;
|
||||
|
||||
@Excel(name = "参数启用", cellType = Excel.ColumnType.NUMERIC, prompt = "1=启用,0=停用")
|
||||
private Integer paramEnabled;
|
||||
|
||||
@Excel(name = "参数备注", width = 20)
|
||||
private String paramRemark;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则参数对象")
|
||||
public class RuleConfigParam {
|
||||
|
||||
@ApiModelProperty("规则编码")
|
||||
private String ruleCode;
|
||||
|
||||
@ApiModelProperty("参数键")
|
||||
private String paramKey;
|
||||
|
||||
@ApiModelProperty("参数值")
|
||||
private String paramVal;
|
||||
|
||||
@ApiModelProperty("值类型(string/number/bool/json)")
|
||||
private String valType;
|
||||
|
||||
@ApiModelProperty("参数名称")
|
||||
private String paramName;
|
||||
|
||||
@ApiModelProperty("排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@ApiModelProperty("是否启用(1是0否)")
|
||||
private Integer enabled;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则配置查询对象")
|
||||
public class RuleConfigQuery {
|
||||
|
||||
@ApiModelProperty("规则编码")
|
||||
private String ruleCode;
|
||||
|
||||
@ApiModelProperty("规则名称")
|
||||
private String ruleName;
|
||||
|
||||
@ApiModelProperty("层级编码(task/action/platform)")
|
||||
private String levelCode;
|
||||
|
||||
@ApiModelProperty("种类编码(select/assign/deploy/config/mode/spacetime/relation/limit)")
|
||||
private String kindCode;
|
||||
|
||||
@ApiModelProperty("模块编码(equipment/target/position/track/group)")
|
||||
private String moduleCode;
|
||||
|
||||
@ApiModelProperty("是否启用(1是0否)")
|
||||
private Integer enabled;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* rule_item_task_type 批量查询行
|
||||
*/
|
||||
@Data
|
||||
public class RuleConfigTaskTypeRow implements Serializable {
|
||||
|
||||
private String ruleCode;
|
||||
|
||||
private String taskTypeCode;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则字典项")
|
||||
public class RuleDictItem {
|
||||
|
||||
@ApiModelProperty("字典类型")
|
||||
private String dictType;
|
||||
|
||||
@ApiModelProperty("字典编码")
|
||||
private String dictCode;
|
||||
|
||||
@ApiModelProperty("字典名称")
|
||||
private String dictName;
|
||||
|
||||
@ApiModelProperty("排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@ApiModelProperty("是否启用(1是0否)")
|
||||
private Integer enabled;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.solution.rule.domain.config;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则参数元数据")
|
||||
public class RuleParamMeta {
|
||||
|
||||
@ApiModelProperty("参数键")
|
||||
private String paramKey;
|
||||
|
||||
@ApiModelProperty("参数名称")
|
||||
private String label;
|
||||
|
||||
@ApiModelProperty("值类型(bool/number/enum/csv/string)")
|
||||
private String valueType;
|
||||
|
||||
@ApiModelProperty("是否必填")
|
||||
private Boolean required;
|
||||
|
||||
@ApiModelProperty("枚举可选值")
|
||||
private List<String> enumOptions;
|
||||
|
||||
@ApiModelProperty("数值最小值")
|
||||
private Double min;
|
||||
|
||||
@ApiModelProperty("数值最大值")
|
||||
private Double max;
|
||||
|
||||
@ApiModelProperty("格式正则")
|
||||
private String pattern;
|
||||
|
||||
@ApiModelProperty("示例值")
|
||||
private String example;
|
||||
|
||||
@ApiModelProperty("说明")
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.solution.rule.domain.config.graph;
|
||||
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* 与 {@code rules/rule.drl} 中「装备/目标/阵位/航迹」四个主规则一一对应,仅用于知识图谱分块展示(与 module_code 一致)。
|
||||
|
||||
*/
|
||||
|
||||
public enum RuleFourBlockDefinition {
|
||||
|
||||
|
||||
|
||||
EQUIPMENT(
|
||||
|
||||
"equipment",
|
||||
|
||||
"装备匹配",
|
||||
|
||||
100,
|
||||
|
||||
Arrays.asList(
|
||||
|
||||
"抽取蓝方任务与武器文案、红方装备类型与名称等,拼接为可对齐的蓝/红文本特征",
|
||||
|
||||
"规则槽关键词与兼容层叠加计分,再乘以全局权重得到每件红装的匹配总分",
|
||||
|
||||
"低于最低入选分则丢弃;并列最高分按既定策略决胜,并写入火力输入与任务名后缀"),
|
||||
|
||||
"DroolsFact(task != null)",
|
||||
|
||||
"mergeDefaultParams(globalParams); equipmentRule($fact, globalParams);"),
|
||||
|
||||
TARGET(
|
||||
|
||||
"target",
|
||||
|
||||
"目标匹配",
|
||||
|
||||
90,
|
||||
|
||||
Arrays.asList(
|
||||
|
||||
"根据命中率映射、射程解析与分配模式,确定每套红方可承载的目标数量与目标标识",
|
||||
|
||||
"向任务 execute 写入目标列表与默认行动类型等执行侧字段",
|
||||
|
||||
"命中率低于阈值时,在轮次与件数限制内补充选取装备并更新任务"),
|
||||
|
||||
"DroolsFact(task != null)",
|
||||
|
||||
"mergeDefaultParams(globalParams); target($fact, globalParams);"),
|
||||
|
||||
POSITION(
|
||||
|
||||
"position",
|
||||
|
||||
"阵位匹配",
|
||||
|
||||
80,
|
||||
|
||||
Arrays.asList(
|
||||
|
||||
"结合锚点模式、航向与部署距离区间、编队样式与间距,计算红方平台目标点位",
|
||||
|
||||
"按平台最小间距离散多个平台坐标,必要时叠加上述默认高度与跟随比例",
|
||||
|
||||
"若启用作战区约束,将越界阵位沿策略回拉到战争区内"),
|
||||
|
||||
"DroolsFact(task != null)",
|
||||
|
||||
"mergeDefaultParams(globalParams); position($fact, globalParams);"),
|
||||
|
||||
TRACK(
|
||||
|
||||
"track",
|
||||
|
||||
"航迹匹配",
|
||||
|
||||
70,
|
||||
|
||||
Arrays.asList(
|
||||
|
||||
"依据蓝方航迹与数据类型判定空中/地面航迹类别,选择对应变形算法生成航迹折线",
|
||||
|
||||
"生成航迹标识并与任务 execute 中的目标绑定移动路由",
|
||||
|
||||
"若启用航迹作战区约束,将航迹点约束在战争区多边形内"),
|
||||
|
||||
"DroolsFact(task != null)",
|
||||
|
||||
"mergeDefaultParams(globalParams); trackRoute($fact, globalParams);");
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.solution.rule.domain.config.graph;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 冻结「参数键 → 四大展示块」归属(与 rule.drl / {@code 002_rule_seed_from_drl.sql} 中的 rule_item.module_code 一致)。
|
||||
* 图谱优先按库表 rule_item_param.rule_code 所属模块归类;若某键仅存在于 globalParams 快照且无法关联模块时,再用本映射兜底。
|
||||
*/
|
||||
public final class RuleFourBlockParamMapping {
|
||||
|
||||
private static final Set<String> EQUIPMENT = set(
|
||||
"weight", "minSelectedScore", "tieBreak", "outputDrawNameSuffix", "ruleSlotCount",
|
||||
"blueRuleKeywords_1", "redRuleKeywords_1", "ruleScore_1",
|
||||
"blueRuleKeywords_2", "redRuleKeywords_2", "ruleScore_2",
|
||||
"blueRuleKeywords_3", "redRuleKeywords_3", "ruleScore_3",
|
||||
"bluePlatformKeywords_air", "redPreferredWhenBlueAir", "airScore",
|
||||
"airTaskKeywords", "airTaskScore",
|
||||
"groundTaskKeywords", "redPreferredWhenGround", "groundScore",
|
||||
"tankKeywords", "redMatchKeywords_tank", "tankScore",
|
||||
"missileKeywords", "redMatchKeywords_missile", "missileScore");
|
||||
private static final Set<String> TARGET = set(
|
||||
"executeTypeDefault", "targetPickMode", "minTargetsPerRed", "maxTargetsPerRedCap",
|
||||
"radToTargetsCsv", "rangeParseRegex", "rangeUnit", "minRangeToAllowAssignKm",
|
||||
"redHitRateThreshold", "maxExtraWeaponsPerTask", "maxSupplementRounds", "extraPickMinScore");
|
||||
private static final Set<String> POSITION = set(
|
||||
"positionRuleEnabled", "positionAnchorMode", "trackPointDirectionMode",
|
||||
"fallbackBearingDeg", "deployDistanceKmMin", "deployDistanceKmMax", "deployDistanceKmDefault",
|
||||
"distanceByPlatformCsv", "formationType", "formationSpacingMeters", "formationHeadingOffsetDeg",
|
||||
"defaultDeployHeight", "heightFollowBlueRatio", "enableWarZoneClamp", "warZoneClampMode",
|
||||
"minInterPlatformDistanceMeters");
|
||||
private static final Set<String> TRACK = set(
|
||||
"trackRuleEnabled", "trackRouteAlgorithm", "trackRouteNameSuffix",
|
||||
"trackAirDataTypeCsv", "trackAirKeywordsCsv", "trackGroundTrackType",
|
||||
"trackFallbackBearingDeg", "enableTrackWarZoneClamp",
|
||||
"trackExtraNodesMax", "trackShortPathSegments",
|
||||
"trackFlankOffsetMeters", "trackFlankSideMode",
|
||||
"trackJamWobbleMeters", "trackJamSegments");
|
||||
|
||||
private RuleFourBlockParamMapping() {
|
||||
}
|
||||
|
||||
private static Set<String> set(String... keys) {
|
||||
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(keys)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param paramKey 参数键
|
||||
* @param slotRuleKeys 形如 blueRuleKeywords_i / redRuleKeywords_i / ruleScore_i,视为装备块
|
||||
*/
|
||||
public static RuleFourBlockDefinition fallbackBlockForKey(String paramKey, boolean slotRuleKeys) {
|
||||
if (paramKey == null || paramKey.isEmpty()) {
|
||||
return RuleFourBlockDefinition.EQUIPMENT;
|
||||
}
|
||||
String k = paramKey.trim();
|
||||
if (slotRuleKeys) {
|
||||
return RuleFourBlockDefinition.EQUIPMENT;
|
||||
}
|
||||
if (EQUIPMENT.contains(k)) {
|
||||
return RuleFourBlockDefinition.EQUIPMENT;
|
||||
}
|
||||
if (TARGET.contains(k)) {
|
||||
return RuleFourBlockDefinition.TARGET;
|
||||
}
|
||||
if (POSITION.contains(k)) {
|
||||
return RuleFourBlockDefinition.POSITION;
|
||||
}
|
||||
if (TRACK.contains(k)) {
|
||||
return RuleFourBlockDefinition.TRACK;
|
||||
}
|
||||
String lower = k.toLowerCase(Locale.ROOT);
|
||||
if (lower.startsWith("track")) {
|
||||
return RuleFourBlockDefinition.TRACK;
|
||||
}
|
||||
if (lower.startsWith("position") || lower.startsWith("deploy") || lower.startsWith("formation")
|
||||
|| lower.contains("warzone") || lower.contains("platform")) {
|
||||
return RuleFourBlockDefinition.POSITION;
|
||||
}
|
||||
if (lower.contains("target") || lower.contains("execute") || lower.contains("assign") || lower.contains("supplement")) {
|
||||
return RuleFourBlockDefinition.TARGET;
|
||||
}
|
||||
return RuleFourBlockDefinition.EQUIPMENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.solution.rule.domain.config.graph;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 四块图谱展示用:每条 rule_item 对应的「运行时产出/写回」中文说明(静态目录,与 RuleFunction 行为一致即可)。
|
||||
*/
|
||||
public final class RuleFourBlockRuleOutputCatalog {
|
||||
|
||||
private static final Map<String, Map<String, List<String>>> BY_MODULE_AND_RULE = new HashMap<>();
|
||||
|
||||
static {
|
||||
Map<String, List<String>> equipment = new HashMap<>();
|
||||
equipment.put("R_TASK_SELECT_BASE", list(
|
||||
"汇总匹配结果并写入火力规则输入(fireRuleInputs 等匹配行)",
|
||||
"为任务名称追加输出后缀(outputDrawNameSuffix)"));
|
||||
equipment.put("R_TASK_SELECT_SLOT_1", list(
|
||||
"若槽1蓝红关键词同时命中,为当前候选装备累加 ruleScore_1×weight 分值"));
|
||||
equipment.put("R_TASK_SELECT_SLOT_2", list(
|
||||
"若槽2蓝红关键词同时命中,累加 ruleScore_2×weight 分值"));
|
||||
equipment.put("R_TASK_SELECT_SLOT_3", list(
|
||||
"若槽3蓝红关键词同时命中,累加 ruleScore_3×weight 分值"));
|
||||
equipment.put("R_TASK_REL_AIR_PLATFORM", list(
|
||||
"空中平台关键词命中时累加 airScore×weight 兼容层分值"));
|
||||
equipment.put("R_TASK_REL_AIR_TASK", list(
|
||||
"空中任务文案命中时累加 airTaskScore×weight 分值"));
|
||||
equipment.put("R_TASK_REL_GROUND_TASK", list(
|
||||
"地面任务与红方偏好同时命中时累加 groundScore×weight 分值"));
|
||||
equipment.put("R_TASK_REL_TANK", list(
|
||||
"坦克类关键词匹配时累加 tankScore×weight 分值"));
|
||||
equipment.put("R_TASK_REL_MISSILE", list(
|
||||
"导弹类关键词匹配时累加 missileScore×weight 分值"));
|
||||
BY_MODULE_AND_RULE.put("equipment", Collections.unmodifiableMap(equipment));
|
||||
|
||||
Map<String, List<String>> target = new HashMap<>();
|
||||
target.put("R_TASK_ASSIGN_TARGET", list(
|
||||
"填充任务 execute 中目标列表与默认行动类型(executeTypeDefault 等)",
|
||||
"按命中率映射与分配模式更新 targetId / 目标数量"));
|
||||
target.put("R_TASK_LIMIT_SUPPLEMENT", list(
|
||||
"命中率低于阈值时按轮次与件数上限补充选取装备并回写任务武器列表"));
|
||||
BY_MODULE_AND_RULE.put("target", Collections.unmodifiableMap(target));
|
||||
|
||||
Map<String, List<String>> position = new HashMap<>();
|
||||
position.put("R_PLATFORM_DEPLOY", list(
|
||||
"计算并写回红方 SubComponents.platform[].positions(部署点、编队间距与高度)"));
|
||||
position.put("R_PLATFORM_SPACETIME", list(
|
||||
"在启用作战区约束时,将越界阵位回拉到 warZoneLocation 多边形内"));
|
||||
BY_MODULE_AND_RULE.put("position", Collections.unmodifiableMap(position));
|
||||
|
||||
Map<String, List<String>> track = new HashMap<>();
|
||||
track.put("R_ACTION_TRACK_ROUTE", list(
|
||||
"生成/更新 TrackParam 中航迹节点,并为 execute 绑定 moveRouteId",
|
||||
"按算法(followBlue 等)变形蓝方航迹并写航迹名称后缀"));
|
||||
track.put("R_ACTION_TRACK_SPACETIME", list(
|
||||
"在启用约束时将航迹点限制在作战区边界内"));
|
||||
BY_MODULE_AND_RULE.put("track", Collections.unmodifiableMap(track));
|
||||
}
|
||||
|
||||
private RuleFourBlockRuleOutputCatalog() {
|
||||
}
|
||||
|
||||
private static List<String> list(String... lines) {
|
||||
List<String> l = new ArrayList<>();
|
||||
for (String s : lines) {
|
||||
if (s != null && !s.isEmpty()) {
|
||||
l.add(s);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param moduleCode equipment / target / position / track
|
||||
* @param ruleCode rule_item.rule_code
|
||||
*/
|
||||
public static List<String> outputsForRule(String moduleCode, String ruleCode) {
|
||||
if (moduleCode == null || ruleCode == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, List<String>> m = BY_MODULE_AND_RULE.get(moduleCode);
|
||||
if (m == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> list = m.get(ruleCode);
|
||||
return list != null ? list : Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.solution.rule.domain.config.graph;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
||||
/**
|
||||
* 参数在「产出」列中的一行静态说明(与 globalParams 生效值拼接展示)。
|
||||
*/
|
||||
public final class RuleParamOutputHint {
|
||||
|
||||
private static final String GENERIC = "写入 globalParams 并由当前块对应 Java 算子读取";
|
||||
|
||||
private RuleParamOutputHint() {
|
||||
}
|
||||
|
||||
public static String effectLine(String paramKey, String moduleCode) {
|
||||
if (ObjectUtil.isEmpty(paramKey)) {
|
||||
return GENERIC;
|
||||
}
|
||||
String k = paramKey.trim();
|
||||
switch (k) {
|
||||
case "weight":
|
||||
return "参与装备/目标等环节的评分加权";
|
||||
case "minSelectedScore":
|
||||
return "低于该分则本规则对应环节不选中/不生效";
|
||||
case "tieBreak":
|
||||
return "同分时的决胜策略(如按装备 ID)";
|
||||
case "outputDrawNameSuffix":
|
||||
return "影响任务/输出名称后缀展示";
|
||||
case "ruleSlotCount":
|
||||
return "规则槽数量上限(装备匹配)";
|
||||
case "executeTypeDefault":
|
||||
return "写入任务 execute 的默认行动类型";
|
||||
case "targetPickMode":
|
||||
return "目标分配挑选模式(轮询/随机等)";
|
||||
case "radToTargetsCsv":
|
||||
return "命中率到目标数量的映射";
|
||||
case "redHitRateThreshold":
|
||||
return "命中率阈值,触发补拿等逻辑";
|
||||
case "maxExtraWeaponsPerTask":
|
||||
case "maxSupplementRounds":
|
||||
case "extraPickMinScore":
|
||||
return "补拿装备轮次与分数约束";
|
||||
case "positionRuleEnabled":
|
||||
case "trackRuleEnabled":
|
||||
case "groupRuleEnabled":
|
||||
return "控制对应环节是否执行";
|
||||
case "deployDistanceKmMin":
|
||||
case "deployDistanceKmMax":
|
||||
case "deployDistanceKmDefault":
|
||||
return "部署距离区间与默认值(阵位)";
|
||||
case "formationType":
|
||||
case "formationSpacingMeters":
|
||||
return "编队几何与间距";
|
||||
case "enableWarZoneClamp":
|
||||
case "enableTrackWarZoneClamp":
|
||||
return "作战区约束开关";
|
||||
case "trackRouteAlgorithm":
|
||||
return "航迹生成算法选择";
|
||||
default:
|
||||
if (k.startsWith("blueRuleKeywords_") || k.startsWith("redRuleKeywords_") || k.startsWith("ruleScore_")) {
|
||||
return "规则槽关键词/分值,参与装备匹配打分";
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(moduleCode) && "track".equals(moduleCode) && k.toLowerCase().startsWith("track")) {
|
||||
return "航迹生成与约束相关配置";
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(moduleCode) && "position".equals(moduleCode)) {
|
||||
return "阵位/部署与空间约束相关配置";
|
||||
}
|
||||
return GENERIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("单个规则决策树分块")
|
||||
public class RuleDecisionBlockVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("分块标识:equipment/target/position/track")
|
||||
private String blockId;
|
||||
|
||||
@ApiModelProperty("模块编码")
|
||||
private String moduleCode;
|
||||
|
||||
@ApiModelProperty("标题")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("Drools 规则名")
|
||||
private String droolsRuleName;
|
||||
|
||||
@ApiModelProperty("salience")
|
||||
private Integer salience;
|
||||
|
||||
@ApiModelProperty("树节点")
|
||||
private List<RuleDecisionNodeVO> nodes;
|
||||
|
||||
public List<RuleDecisionNodeVO> safeNodes() {
|
||||
if (nodes == null) {
|
||||
nodes = new ArrayList<>();
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则决策树节点")
|
||||
public class RuleDecisionNodeVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("节点唯一键")
|
||||
private String key;
|
||||
|
||||
@ApiModelProperty("节点标题")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("节点类型")
|
||||
private String nodeType;
|
||||
|
||||
@ApiModelProperty("补充说明")
|
||||
private String description;
|
||||
|
||||
@ApiModelProperty("右侧值文本")
|
||||
private String valueText;
|
||||
|
||||
@ApiModelProperty("子节点")
|
||||
private List<RuleDecisionNodeVO> children;
|
||||
|
||||
public List<RuleDecisionNodeVO> safeChildren() {
|
||||
if (children == null) {
|
||||
children = new ArrayList<>();
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则决策树")
|
||||
public class RuleDecisionTreeVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("与运行时 globalParams 一致的参数快照")
|
||||
private Map<String, Object> globalParamsPreview;
|
||||
|
||||
@ApiModelProperty("四大规则分块")
|
||||
private List<RuleDecisionBlockVO> blocks;
|
||||
|
||||
public Map<String, Object> safeGlobalParamsPreview() {
|
||||
if (globalParamsPreview == null) {
|
||||
globalParamsPreview = new LinkedHashMap<>();
|
||||
}
|
||||
return globalParamsPreview;
|
||||
}
|
||||
|
||||
public List<RuleDecisionBlockVO> safeBlocks() {
|
||||
if (blocks == null) {
|
||||
blocks = new ArrayList<>();
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("单个分块(环形参数 + If/Then 中枢)")
|
||||
public class RuleFourBlockClusterVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("块标识:equipment/target/position/track")
|
||||
private String blockId;
|
||||
|
||||
@ApiModelProperty("模块编码,对应 rule_item.module_code")
|
||||
private String moduleCode;
|
||||
|
||||
@ApiModelProperty("界面标题(如 装备匹配)")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("Drools 规则名(如 装备匹配)")
|
||||
private String droolsRuleName;
|
||||
|
||||
@ApiModelProperty("salience")
|
||||
private Integer salience;
|
||||
|
||||
@ApiModelProperty("Drools when 表达式摘要")
|
||||
private String whenExpr;
|
||||
|
||||
@ApiModelProperty("then 动作(含 mergeDefaultParams + Java 调用)")
|
||||
private String thenAction;
|
||||
|
||||
@ApiModelProperty("中枢 + 环形参数节点与边")
|
||||
private RuleGraphVO graph;
|
||||
|
||||
@ApiModelProperty("该块内每条规则的每个参数:When/Then/产出(表格)")
|
||||
private List<RuleFourBlockParamRowVO> paramRows;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@ApiModel("四块分区内单条参数展示行")
|
||||
public class RuleFourBlockParamRowVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("规则编码")
|
||||
private String ruleCode;
|
||||
|
||||
@ApiModelProperty("规则名称")
|
||||
private String ruleName;
|
||||
|
||||
@ApiModelProperty("参数键")
|
||||
private String paramKey;
|
||||
|
||||
@ApiModelProperty("参数业务名")
|
||||
private String paramName;
|
||||
|
||||
@ApiModelProperty("When(规则条件或块级回退)")
|
||||
private String whenText;
|
||||
|
||||
@ApiModelProperty("Then(规则动作或块级回退)")
|
||||
private String thenText;
|
||||
|
||||
@ApiModelProperty("产出说明(生效值 + 静态提示)")
|
||||
private String outputText;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@ApiModel("四块规则知识图谱(装备/目标/阵位/航迹)")
|
||||
public class RuleFourBlocksGraphVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("与运行时 KieSession globalParams 一致的参数快照(启用规则与参数合并后)")
|
||||
private Map<String, Object> globalParamsPreview;
|
||||
|
||||
@ApiModelProperty("四个分块,每块含中枢 If/Then 与子图节点边")
|
||||
private List<RuleFourBlockClusterVO> blocks;
|
||||
|
||||
public Map<String, Object> safeGlobalParamsPreview() {
|
||||
if (globalParamsPreview == null) {
|
||||
globalParamsPreview = new LinkedHashMap<>();
|
||||
}
|
||||
return globalParamsPreview;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则知识图谱边")
|
||||
public class RuleGraphEdgeVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("边 id")
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty("起点节点 id")
|
||||
private String source;
|
||||
|
||||
@ApiModelProperty("终点节点 id")
|
||||
private String target;
|
||||
|
||||
@ApiModelProperty("边类型")
|
||||
private String edgeType;
|
||||
|
||||
@ApiModelProperty("边上展示文案")
|
||||
private String label;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则知识图谱节点")
|
||||
public class RuleGraphNodeVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("全局唯一节点 id")
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty("展示标题")
|
||||
private String label;
|
||||
|
||||
@ApiModelProperty("节点类型:level/kind/module/rule/param/taskType")
|
||||
private String nodeType;
|
||||
|
||||
@ApiModelProperty("附加属性(如 ruleCode、paramKey、字典编码等)")
|
||||
private Map<String, Object> payload;
|
||||
|
||||
public Map<String, Object> safePayload() {
|
||||
if (payload == null) {
|
||||
payload = new HashMap<>();
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.solution.rule.domain.config.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel("规则知识图谱数据")
|
||||
public class RuleGraphVO implements Serializable {
|
||||
|
||||
@ApiModelProperty("节点列表")
|
||||
private List<RuleGraphNodeVO> nodes;
|
||||
|
||||
@ApiModelProperty("边列表")
|
||||
private List<RuleGraphEdgeVO> edges;
|
||||
|
||||
@ApiModelProperty("前端布局提示:如 radial_hub 使用径向圆心布局")
|
||||
private String layoutHint;
|
||||
|
||||
@ApiModelProperty("径向布局时的中心节点 id(通常为 drools_facade)")
|
||||
private String focusNodeId;
|
||||
|
||||
public List<RuleGraphNodeVO> safeNodes() {
|
||||
if (nodes == null) {
|
||||
nodes = new ArrayList<>();
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public List<RuleGraphEdgeVO> safeEdges() {
|
||||
if (edges == null) {
|
||||
edges = new ArrayList<>();
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class AssignTargetItem {
|
||||
|
||||
private String task;
|
||||
|
||||
private String type;
|
||||
|
||||
private List<String> target = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DataOutput {
|
||||
|
||||
@JsonProperty("assign_target")
|
||||
private List<AssignTargetItem> assignTarget = new ArrayList<>();
|
||||
|
||||
private List<TaskItem> task = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3ExecuteBlock {
|
||||
|
||||
private List<Kj3TargetItem> targetList;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3ForceSide {
|
||||
|
||||
@JsonProperty("ObjectHandle")
|
||||
private String objectHandle;
|
||||
|
||||
@JsonProperty("ForceSideName")
|
||||
private String forceSideName;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3MilitaryItem {
|
||||
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("OwnerForceSide")
|
||||
private String ownerForceSide;
|
||||
|
||||
@JsonProperty("EquipmentID")
|
||||
private String equipmentId;
|
||||
|
||||
@JsonProperty("Platform_type")
|
||||
private String platformType;
|
||||
|
||||
@JsonProperty("SubComponents")
|
||||
private Kj3SubComponents subComponents;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3MilitaryPlatformComponent {
|
||||
|
||||
@JsonProperty("TrackParamId")
|
||||
private String trackParamId;
|
||||
|
||||
private List<Double> positions;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3MilitaryScenario {
|
||||
|
||||
@JsonProperty("Tasks")
|
||||
private List<Kj3TaskNode> tasks;
|
||||
|
||||
@JsonProperty("ForceSides")
|
||||
private List<Kj3ForceSide> forceSides;
|
||||
|
||||
@JsonProperty("Military")
|
||||
private List<Kj3MilitaryItem> military;
|
||||
|
||||
@JsonProperty("RefAttributeObject")
|
||||
private Kj3RefAttributeObject refAttributeObject;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Kj3PostResult {
|
||||
|
||||
private boolean success;
|
||||
|
||||
private String statusMessage;
|
||||
|
||||
private String postUrl;
|
||||
|
||||
private int taskCount;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3RefAttributeObject {
|
||||
|
||||
@JsonProperty("TrackParam")
|
||||
private Map<String, Kj3TrackParam> trackParam;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3Root {
|
||||
|
||||
@JsonProperty("MilitaryScenario")
|
||||
private Kj3MilitaryScenario militaryScenario;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3SubComponents {
|
||||
|
||||
private List<Kj3MilitaryPlatformComponent> platform;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3TargetItem {
|
||||
|
||||
private String targetId;
|
||||
|
||||
@JsonProperty("cruiseRouteId")
|
||||
private String cruiseRouteId;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3TaskNode {
|
||||
|
||||
private String name;
|
||||
|
||||
private String side;
|
||||
|
||||
private Kj3TaskPayload task;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3TaskPayload {
|
||||
|
||||
private String name;
|
||||
|
||||
private String side;
|
||||
|
||||
private String type;
|
||||
|
||||
@JsonProperty("at_time")
|
||||
private String atTime;
|
||||
|
||||
private List<Kj3ExecuteBlock> execute;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3TrackParam {
|
||||
|
||||
@JsonProperty("TrackPoints")
|
||||
private List<Kj3TrackPoint> trackPoints;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Kj3TrackPoint {
|
||||
|
||||
private String longitude;
|
||||
|
||||
private String latitude;
|
||||
|
||||
private String height;
|
||||
|
||||
private String speed;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PlatformItem {
|
||||
|
||||
private String name;
|
||||
|
||||
@JsonProperty("platform_type")
|
||||
private String platformType;
|
||||
|
||||
private String longitude;
|
||||
|
||||
private String latitude;
|
||||
|
||||
private Integer altitude;
|
||||
|
||||
private List<RoutePoint> route = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RoutePoint {
|
||||
|
||||
private String longitude;
|
||||
|
||||
private String latitude;
|
||||
|
||||
private Integer altitude;
|
||||
|
||||
private Integer speed;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class TaskItem {
|
||||
|
||||
@JsonProperty("task_name")
|
||||
private String taskName;
|
||||
|
||||
private List<PlatformItem> platform = new ArrayList<>();
|
||||
|
||||
private TaskTime time;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.solution.rule.domain.tasks;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TaskTime {
|
||||
|
||||
private Integer begin;
|
||||
|
||||
private Integer end;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.solution.rule.mapper;
|
||||
|
||||
import com.solution.rule.domain.config.RuleConfig;
|
||||
import com.solution.rule.domain.config.RuleConfigParam;
|
||||
import com.solution.rule.domain.config.RuleConfigQuery;
|
||||
import com.solution.rule.domain.config.RuleConfigTaskTypeRow;
|
||||
import com.solution.rule.domain.config.RuleDictItem;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RuleConfigMapper {
|
||||
|
||||
List<RuleConfig> selectRuleConfigList(RuleConfigQuery query);
|
||||
|
||||
RuleConfig selectRuleConfigByCode(@Param("ruleCode") String ruleCode);
|
||||
|
||||
int countByRuleCode(@Param("ruleCode") String ruleCode);
|
||||
|
||||
int insertRuleConfig(RuleConfig ruleConfig);
|
||||
|
||||
int updateRuleConfig(RuleConfig ruleConfig);
|
||||
|
||||
int deleteRuleConfigByCodes(@Param("ruleCodes") String[] ruleCodes);
|
||||
|
||||
List<RuleConfigParam> selectParamsByRuleCode(@Param("ruleCode") String ruleCode);
|
||||
|
||||
List<RuleConfigParam> selectParamsByRuleCodes(@Param("ruleCodes") List<String> ruleCodes);
|
||||
|
||||
int deleteParamsByRuleCodes(@Param("ruleCodes") String[] ruleCodes);
|
||||
|
||||
int insertParamsBatch(@Param("params") List<RuleConfigParam> params);
|
||||
|
||||
List<String> selectTaskTypesByRuleCode(@Param("ruleCode") String ruleCode);
|
||||
|
||||
List<RuleConfigTaskTypeRow> selectTaskTypesByRuleCodes(@Param("ruleCodes") List<String> ruleCodes);
|
||||
|
||||
int deleteTaskTypesByRuleCodes(@Param("ruleCodes") String[] ruleCodes);
|
||||
|
||||
int insertTaskTypesBatch(@Param("ruleCode") String ruleCode, @Param("taskTypes") List<String> taskTypes);
|
||||
|
||||
List<RuleDictItem> selectDictByType(@Param("dictType") String dictType);
|
||||
|
||||
List<RuleConfigParam> selectEnabledParamsForGlobal();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.solution.rule.service;
|
||||
|
||||
import com.solution.rule.domain.config.RuleConfig;
|
||||
import com.solution.rule.domain.config.RuleConfigExcelRow;
|
||||
import com.solution.rule.domain.config.RuleConfigQuery;
|
||||
import com.solution.rule.domain.config.RuleDictItem;
|
||||
import com.solution.rule.domain.config.RuleParamMeta;
|
||||
import com.solution.rule.domain.config.vo.RuleDecisionTreeVO;
|
||||
import com.solution.rule.domain.config.vo.RuleFourBlocksGraphVO;
|
||||
import com.solution.rule.domain.config.vo.RuleGraphVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IRuleConfigService {
|
||||
|
||||
List<RuleConfig> selectRuleConfigList(RuleConfigQuery query);
|
||||
|
||||
RuleConfig selectRuleConfigByCode(String ruleCode);
|
||||
|
||||
int insertRuleConfig(RuleConfig ruleConfig);
|
||||
|
||||
int updateRuleConfig(RuleConfig ruleConfig);
|
||||
|
||||
int deleteRuleConfigByCodes(String[] ruleCodes);
|
||||
|
||||
List<RuleDictItem> selectDictByType(String dictType);
|
||||
|
||||
Map<String, Object> loadEnabledGlobalParams();
|
||||
|
||||
List<RuleParamMeta> selectParamMetaList();
|
||||
|
||||
List<RuleConfigExcelRow> exportRuleConfigRows(RuleConfigQuery query);
|
||||
|
||||
String importRuleConfigRows(List<RuleConfigExcelRow> rows, boolean updateSupport);
|
||||
|
||||
/**
|
||||
* 根据当前页规则主数据构建知识图谱(节点与边),参数与任务类型从库批量加载。
|
||||
*/
|
||||
RuleGraphVO buildKnowledgeGraph(List<RuleConfig> ruleConfigs);
|
||||
|
||||
RuleDecisionTreeVO buildDecisionTree(List<RuleConfig> ruleConfigs);
|
||||
|
||||
/**
|
||||
* 四块规则知识图谱(装备/目标/阵位/航迹):中枢 If/Then + 环形参数,参数值与 {@link #loadEnabledGlobalParams()} 一致。
|
||||
*/
|
||||
RuleFourBlocksGraphVO buildFourBlocksKnowledgeGraph();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.solution.rule.service;
|
||||
|
||||
import com.solution.rule.domain.tasks.Kj3PostResult;
|
||||
import com.solution.rule.domain.tasks.Kj3Root;
|
||||
|
||||
public interface Kj3TaskDataService {
|
||||
|
||||
/**
|
||||
* 解析接口入参 JSON,仅提取红方任务,转换为 data 结构后发送 HTTP POST。
|
||||
*
|
||||
* @param kj3Json 传入的 kj-3 JSON 字符串
|
||||
* @return 发送结果
|
||||
*/
|
||||
Kj3PostResult parseAndPostRedTasks(String kj3Json);
|
||||
|
||||
/**
|
||||
* 解析接口入参对象,仅提取红方任务,转换为 data 结构后发送 HTTP POST。
|
||||
*
|
||||
* @param root 传入的 kj-3 对象
|
||||
* @return 发送结果
|
||||
*/
|
||||
Kj3PostResult parseAndPostRedTasks(Kj3Root root);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.solution.rule.service;
|
||||
|
||||
public interface RuleDrlSyncService {
|
||||
|
||||
void syncGlobalParamsToDrl();
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
|
||||
import com.solution.rule.domain.vo.WeaponModelVO;
|
||||
import com.solution.rule.mapper.FireRuleMapper;
|
||||
import com.solution.rule.service.FireRuleService;
|
||||
import com.solution.rule.service.IRuleConfigService;
|
||||
import com.solution.rule.simpstrategy.FireRUleType;
|
||||
import com.solution.rule.simpstrategy.FireRuleStrategy;
|
||||
import com.solution.rule.simpstrategy.FireRuleStrategyFactory;
|
||||
@@ -51,6 +52,8 @@ public class FireRuleServiceImpl implements FireRuleService {
|
||||
|
||||
@Autowired
|
||||
private KieBase kieBase;
|
||||
@Autowired
|
||||
private IRuleConfigService ruleConfigService;
|
||||
|
||||
/* @Override
|
||||
public WeaponModelVO execute(Integer sceneType, WeaponModelDTO weaponModelDTO) {
|
||||
@@ -209,7 +212,8 @@ public class FireRuleServiceImpl implements FireRuleService {
|
||||
}
|
||||
//创建KieSession
|
||||
KieSession kieSession = kieBase.newKieSession();
|
||||
Map<String, Object> globalParams = new HashMap<>();
|
||||
// Map<String, Object> globalParams = new HashMap<>();
|
||||
Map<String, Object> globalParams = ruleConfigService.loadEnabledGlobalParams();
|
||||
// globalParams.putAll(FireRuleMatchDefaultParams.defaults());
|
||||
kieSession.setGlobal("globalParams", globalParams);
|
||||
//获取红方阵营id
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
package com.solution.rule.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.solution.rule.domain.tasks.AssignTargetItem;
|
||||
import com.solution.rule.domain.tasks.DataOutput;
|
||||
import com.solution.rule.domain.tasks.Kj3ExecuteBlock;
|
||||
import com.solution.rule.domain.tasks.Kj3ForceSide;
|
||||
import com.solution.rule.domain.tasks.Kj3MilitaryItem;
|
||||
import com.solution.rule.domain.tasks.Kj3MilitaryPlatformComponent;
|
||||
import com.solution.rule.domain.tasks.Kj3MilitaryScenario;
|
||||
import com.solution.rule.domain.tasks.Kj3PostResult;
|
||||
import com.solution.rule.domain.tasks.Kj3RefAttributeObject;
|
||||
import com.solution.rule.domain.tasks.Kj3Root;
|
||||
import com.solution.rule.domain.tasks.Kj3TaskNode;
|
||||
import com.solution.rule.domain.tasks.Kj3TaskPayload;
|
||||
import com.solution.rule.domain.tasks.Kj3TrackParam;
|
||||
import com.solution.rule.domain.tasks.Kj3TrackPoint;
|
||||
import com.solution.rule.domain.tasks.Kj3TargetItem;
|
||||
import com.solution.rule.domain.tasks.PlatformItem;
|
||||
import com.solution.rule.domain.tasks.RoutePoint;
|
||||
import com.solution.rule.domain.tasks.TaskItem;
|
||||
import com.solution.rule.domain.tasks.TaskTime;
|
||||
import com.solution.rule.service.Kj3TaskDataService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class Kj3TaskDataServiceImpl implements Kj3TaskDataService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Kj3TaskDataServiceImpl.class);
|
||||
|
||||
private static final String DATA_POST_URL = "http://localhost:5000/bhtree/zhitangCompanyData";
|
||||
private static final String URL_PLACEHOLDER = "http://TODO-REPLACE";
|
||||
|
||||
private static final String RED_SIDE = "红方";
|
||||
|
||||
private static final String DEFAULT_ASSIGN_TYPE = "fire";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Kj3PostResult parseAndPostRedTasks(String kj3Json) {
|
||||
if (ObjectUtil.isEmpty(kj3Json)) {
|
||||
throw new IllegalArgumentException("kj3Json 不能为空");
|
||||
}
|
||||
Kj3Root root = parseKj3Json(kj3Json);
|
||||
return parseAndPostRedTasks(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kj3PostResult parseAndPostRedTasks(Kj3Root root) {
|
||||
if (ObjectUtil.isEmpty(root)) {
|
||||
throw new IllegalArgumentException("kj3 入参对象不能为空");
|
||||
}
|
||||
DataOutput output = transformToDataOutput(root);
|
||||
String payload = writeJson(output);
|
||||
log.info("kj3 转换后的 data 输出: {}", payload);
|
||||
Kj3PostResult result = new Kj3PostResult();
|
||||
result.setPostUrl(DATA_POST_URL);
|
||||
result.setTaskCount(output.getTask().size());
|
||||
if (!isPostUrlConfigured()) {
|
||||
result.setSuccess(false);
|
||||
result.setStatusMessage("POST URL 未配置,请将 DATA_POST_URL 从占位值替换为真实地址");
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
String response = postJson(payload);
|
||||
result.setSuccess(true);
|
||||
result.setStatusMessage(response);
|
||||
} catch (RuntimeException ex) {
|
||||
result.setSuccess(false);
|
||||
result.setStatusMessage(ex.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isPostUrlConfigured() {
|
||||
return ObjectUtil.isNotEmpty(DATA_POST_URL) && !URL_PLACEHOLDER.equalsIgnoreCase(DATA_POST_URL);
|
||||
}
|
||||
|
||||
private Kj3Root parseKj3Json(String kj3Json) {
|
||||
try {
|
||||
return objectMapper.readValue(kj3Json, Kj3Root.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("解析接口入参 kj-3 JSON 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private DataOutput transformToDataOutput(Kj3Root root) {
|
||||
DataOutput output = new DataOutput();
|
||||
Kj3MilitaryScenario scenario = root == null ? null : root.getMilitaryScenario();
|
||||
List<Kj3TaskNode> tasks = scenario == null ? Collections.emptyList() : scenario.getTasks();
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return output;
|
||||
}
|
||||
String redForceSideId = getRedForceSideId(scenario);
|
||||
|
||||
List<Kj3TaskNode> redTasks = tasks.stream()
|
||||
.filter(this::isRedTask)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Kj3TaskNode redTask : redTasks) {
|
||||
String taskName = resolveTaskName(redTask);
|
||||
Kj3TaskPayload payload = redTask.getTask();
|
||||
|
||||
TaskItem taskItem = new TaskItem();
|
||||
taskItem.setTaskName(taskName);
|
||||
taskItem.setTime(buildTaskTime(payload));
|
||||
taskItem.setPlatform(buildPlatformsForTask(scenario, redForceSideId, payload));
|
||||
output.getTask().add(taskItem);
|
||||
|
||||
AssignTargetItem assignTargetItem = new AssignTargetItem();
|
||||
assignTargetItem.setTask(taskName);
|
||||
assignTargetItem.setType(resolveTaskType(payload));
|
||||
assignTargetItem.setTarget(extractTargets(payload));
|
||||
output.getAssignTarget().add(assignTargetItem);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private boolean isRedTask(Kj3TaskNode taskNode) {
|
||||
if (taskNode == null) {
|
||||
return false;
|
||||
}
|
||||
if (RED_SIDE.equals(taskNode.getSide())) {
|
||||
return true;
|
||||
}
|
||||
Kj3TaskPayload payload = taskNode.getTask();
|
||||
return payload != null && RED_SIDE.equals(payload.getSide());
|
||||
}
|
||||
|
||||
private String resolveTaskName(Kj3TaskNode taskNode) {
|
||||
Kj3TaskPayload payload = taskNode.getTask();
|
||||
if (payload != null && ObjectUtil.isNotEmpty(payload.getName())) {
|
||||
return payload.getName();
|
||||
}
|
||||
return taskNode.getName();
|
||||
}
|
||||
|
||||
private String resolveTaskType(Kj3TaskPayload payload) {
|
||||
if (payload != null && ObjectUtil.isNotEmpty(payload.getType())) {
|
||||
return payload.getType();
|
||||
}
|
||||
return DEFAULT_ASSIGN_TYPE;
|
||||
}
|
||||
|
||||
private String getRedForceSideId(Kj3MilitaryScenario scenario) {
|
||||
if (scenario == null || CollUtil.isEmpty(scenario.getForceSides())) {
|
||||
return "";
|
||||
}
|
||||
for (Kj3ForceSide forceSide : scenario.getForceSides()) {
|
||||
if (forceSide != null && RED_SIDE.equals(forceSide.getForceSideName())) {
|
||||
return forceSide.getObjectHandle();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private List<PlatformItem> buildRedPlatforms(Kj3MilitaryScenario scenario, String redForceSideId) {
|
||||
if (scenario == null || CollUtil.isEmpty(scenario.getMilitary()) || ObjectUtil.isEmpty(redForceSideId)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Map<String, Kj3TrackParam> trackParamMap = getTrackParamMap(scenario);
|
||||
List<PlatformItem> platforms = new ArrayList<>();
|
||||
for (Kj3MilitaryItem militaryItem : scenario.getMilitary()) {
|
||||
if (militaryItem == null || !redForceSideId.equals(militaryItem.getOwnerForceSide())) {
|
||||
continue;
|
||||
}
|
||||
PlatformItem platformItem = mapMilitaryToPlatform(militaryItem, trackParamMap);
|
||||
platforms.add(platformItem);
|
||||
}
|
||||
return platforms;
|
||||
}
|
||||
|
||||
private List<PlatformItem> buildPlatformsForTask(Kj3MilitaryScenario scenario, String redForceSideId, Kj3TaskPayload payload) {
|
||||
List<PlatformItem> redPlatforms = buildRedPlatforms(scenario, redForceSideId);
|
||||
if (CollUtil.isEmpty(redPlatforms)) {
|
||||
return redPlatforms;
|
||||
}
|
||||
Map<String, Kj3TrackParam> trackParamMap = getTrackParamMap(scenario);
|
||||
String fallbackRouteId = resolveFallbackRouteId(payload);
|
||||
if (ObjectUtil.isEmpty(fallbackRouteId)) {
|
||||
return redPlatforms;
|
||||
}
|
||||
for (PlatformItem platform : redPlatforms) {
|
||||
if (CollUtil.isNotEmpty(platform.getRoute())) {
|
||||
continue;
|
||||
}
|
||||
platform.setRoute(buildRoute(fallbackRouteId, trackParamMap));
|
||||
}
|
||||
return redPlatforms;
|
||||
}
|
||||
|
||||
private Map<String, Kj3TrackParam> getTrackParamMap(Kj3MilitaryScenario scenario) {
|
||||
Kj3RefAttributeObject refAttributeObject = scenario.getRefAttributeObject();
|
||||
if (refAttributeObject == null || refAttributeObject.getTrackParam() == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return refAttributeObject.getTrackParam();
|
||||
}
|
||||
|
||||
private PlatformItem mapMilitaryToPlatform(Kj3MilitaryItem militaryItem, Map<String, Kj3TrackParam> trackParamMap) {
|
||||
PlatformItem platformItem = new PlatformItem();
|
||||
platformItem.setName(militaryItem.getName());
|
||||
platformItem.setPlatformType(militaryItem.getPlatformType());
|
||||
|
||||
Kj3MilitaryPlatformComponent positionSourceComponent = resolvePositionSourceComponent(militaryItem);
|
||||
fillPosition(platformItem, positionSourceComponent);
|
||||
String trackParamId = resolveTrackParamId(militaryItem);
|
||||
platformItem.setRoute(buildRoute(trackParamId, trackParamMap));
|
||||
return platformItem;
|
||||
}
|
||||
|
||||
private String resolveFallbackRouteId(Kj3TaskPayload payload) {
|
||||
if (payload == null || CollUtil.isEmpty(payload.getExecute())) {
|
||||
return "";
|
||||
}
|
||||
for (Kj3ExecuteBlock executeBlock : payload.getExecute()) {
|
||||
if (executeBlock == null || CollUtil.isEmpty(executeBlock.getTargetList())) {
|
||||
continue;
|
||||
}
|
||||
for (Kj3TargetItem targetItem : executeBlock.getTargetList()) {
|
||||
if (targetItem != null && ObjectUtil.isNotEmpty(targetItem.getCruiseRouteId())) {
|
||||
return targetItem.getCruiseRouteId();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private Kj3MilitaryPlatformComponent resolvePositionSourceComponent(Kj3MilitaryItem militaryItem) {
|
||||
if (militaryItem.getSubComponents() == null || CollUtil.isEmpty(militaryItem.getSubComponents().getPlatform())) {
|
||||
return null;
|
||||
}
|
||||
for (Kj3MilitaryPlatformComponent component : militaryItem.getSubComponents().getPlatform()) {
|
||||
if (component != null && CollUtil.isNotEmpty(component.getPositions())) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
return militaryItem.getSubComponents().getPlatform().get(0);
|
||||
}
|
||||
|
||||
private String resolveTrackParamId(Kj3MilitaryItem militaryItem) {
|
||||
if (militaryItem.getSubComponents() == null || CollUtil.isEmpty(militaryItem.getSubComponents().getPlatform())) {
|
||||
return "";
|
||||
}
|
||||
for (Kj3MilitaryPlatformComponent component : militaryItem.getSubComponents().getPlatform()) {
|
||||
if (component != null && ObjectUtil.isNotEmpty(component.getTrackParamId())) {
|
||||
return component.getTrackParamId();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void fillPosition(PlatformItem platformItem, Kj3MilitaryPlatformComponent component) {
|
||||
if (component == null || CollUtil.isEmpty(component.getPositions())) {
|
||||
return;
|
||||
}
|
||||
List<Double> positions = component.getPositions();
|
||||
if (positions.size() > 0 && positions.get(0) != null) {
|
||||
platformItem.setLongitude(String.valueOf(positions.get(0)));
|
||||
}
|
||||
if (positions.size() > 1 && positions.get(1) != null) {
|
||||
platformItem.setLatitude(String.valueOf(positions.get(1)));
|
||||
}
|
||||
if (positions.size() > 2 && positions.get(2) != null) {
|
||||
platformItem.setAltitude((int) Math.round(positions.get(2)));
|
||||
}
|
||||
}
|
||||
|
||||
private List<RoutePoint> buildRoute(String trackParamId, Map<String, Kj3TrackParam> trackParamMap) {
|
||||
if (ObjectUtil.isEmpty(trackParamId)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Kj3TrackParam trackParam = trackParamMap.get(trackParamId);
|
||||
if (trackParam == null || CollUtil.isEmpty(trackParam.getTrackPoints())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<RoutePoint> routes = new ArrayList<>();
|
||||
for (Kj3TrackPoint trackPoint : trackParam.getTrackPoints()) {
|
||||
if (trackPoint == null) {
|
||||
continue;
|
||||
}
|
||||
RoutePoint routePoint = new RoutePoint();
|
||||
routePoint.setLongitude(trackPoint.getLongitude());
|
||||
routePoint.setLatitude(trackPoint.getLatitude());
|
||||
routePoint.setAltitude(parseInteger(trackPoint.getHeight()));
|
||||
routePoint.setSpeed(parseInteger(trackPoint.getSpeed()));
|
||||
routes.add(routePoint);
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
private TaskTime buildTaskTime(Kj3TaskPayload payload) {
|
||||
int begin = parseAtTime(payload);
|
||||
TaskTime taskTime = new TaskTime();
|
||||
taskTime.setBegin(begin);
|
||||
taskTime.setEnd(begin);
|
||||
return taskTime;
|
||||
}
|
||||
|
||||
private int parseAtTime(Kj3TaskPayload payload) {
|
||||
if (payload == null || ObjectUtil.isEmpty(payload.getAtTime())) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(payload.getAtTime().trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private Integer parseInteger(String value) {
|
||||
if (ObjectUtil.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (int) Math.round(Double.parseDouble(value.trim()));
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> extractTargets(Kj3TaskPayload payload) {
|
||||
if (payload == null || CollUtil.isEmpty(payload.getExecute())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Set<String> uniqueTargets = new LinkedHashSet<>();
|
||||
for (Kj3ExecuteBlock executeBlock : payload.getExecute()) {
|
||||
if (executeBlock == null || CollUtil.isEmpty(executeBlock.getTargetList())) {
|
||||
continue;
|
||||
}
|
||||
for (Kj3TargetItem targetItem : executeBlock.getTargetList()) {
|
||||
if (targetItem == null || ObjectUtil.isEmpty(targetItem.getTargetId())) {
|
||||
continue;
|
||||
}
|
||||
uniqueTargets.add(targetItem.getTargetId());
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(uniqueTargets);
|
||||
}
|
||||
|
||||
private String writeJson(DataOutput output) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(output);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("序列化 data 输出失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String postJson(String payload) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(DATA_POST_URL);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
|
||||
connection.setConnectTimeout(10000);
|
||||
connection.setReadTimeout(10000);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
try (OutputStream outputStream = connection.getOutputStream()) {
|
||||
outputStream.write(payload.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
int status = connection.getResponseCode();
|
||||
try (InputStream responseStream = status >= 400 ? connection.getErrorStream() : connection.getInputStream()) {
|
||||
String responseBody = readResponse(responseStream);
|
||||
return "HTTP " + status + " " + responseBody;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (e instanceof UnknownHostException) {
|
||||
throw new RuntimeException("POST data 输出失败: 无法解析主机,请检查 DATA_POST_URL=" + DATA_POST_URL, e);
|
||||
}
|
||||
throw new RuntimeException("POST data 输出失败: " + e.getMessage(), e);
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String readResponse(InputStream inputStream) throws IOException {
|
||||
if (inputStream == null) {
|
||||
return "";
|
||||
}
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
return response.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
package com.solution.rule.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.solution.rule.domain.config.RuleConfigParam;
|
||||
import com.solution.rule.mapper.RuleConfigMapper;
|
||||
import com.solution.rule.service.RuleDrlSyncService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class RuleDrlSyncServiceImpl implements RuleDrlSyncService {
|
||||
|
||||
@Autowired
|
||||
private RuleConfigMapper ruleConfigMapper;
|
||||
|
||||
@Override
|
||||
public void syncGlobalParamsToDrl() {
|
||||
Path drlPath = resolveDrlPath();
|
||||
try {
|
||||
String content = Files.readString(drlPath, StandardCharsets.UTF_8);
|
||||
String generated = generateParamPutLines(ruleConfigMapper.selectEnabledParamsForGlobal());
|
||||
String newContent = replaceBuildParamBody(content, generated);
|
||||
Files.writeString(drlPath, newContent, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("同步 rule.drl 失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolveDrlPath() {
|
||||
Path root = Paths.get(System.getProperty("user.dir"));
|
||||
Path path = root.resolve("auto-solution-rule/src/main/resources/rules/rule.drl");
|
||||
if (Files.exists(path)) {
|
||||
return path;
|
||||
}
|
||||
Path fallback = root.resolve("src/main/resources/rules/rule.drl");
|
||||
if (Files.exists(fallback)) {
|
||||
return fallback;
|
||||
}
|
||||
throw new RuntimeException("未找到 rule.drl 文件路径");
|
||||
}
|
||||
|
||||
private String generateParamPutLines(List<RuleConfigParam> params) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" // ===== 以下由规则配置表自动同步生成(请勿手改 param.put 段) =====\n");
|
||||
if (CollUtil.isEmpty(params)) {
|
||||
return sb.toString();
|
||||
}
|
||||
for (RuleConfigParam param : params) {
|
||||
if (param == null || param.getParamKey() == null) {
|
||||
continue;
|
||||
}
|
||||
String valueExpr = toDrlValueExpr(param.getParamVal(), param.getValType());
|
||||
sb.append(" param.put(\"")
|
||||
.append(escapeJavaString(param.getParamKey()))
|
||||
.append("\", ")
|
||||
.append(valueExpr)
|
||||
.append(");\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String toDrlValueExpr(String val, String valType) {
|
||||
if ("bool".equalsIgnoreCase(valType) || "boolean".equalsIgnoreCase(valType)) {
|
||||
return String.valueOf(Boolean.parseBoolean(val));
|
||||
}
|
||||
if ("number".equalsIgnoreCase(valType)) {
|
||||
if (val == null || val.trim().isEmpty()) {
|
||||
return "0";
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
// string/json 统一按字符串写入,多个值用英文逗号分隔时保持原样,不做拆分
|
||||
return "\"" + escapeJavaString(val == null ? "" : val) + "\"";
|
||||
}
|
||||
|
||||
private String replaceBuildParamBody(String content, String generatedLines) {
|
||||
String marker = " // ===== 以下由规则配置表自动同步生成(请勿手改 param.put 段) =====";
|
||||
int buildParamStart = content.indexOf("function Map buildParam(){");
|
||||
int returnPos = content.indexOf(" return param;", buildParamStart);
|
||||
if (buildParamStart < 0 || returnPos < 0) {
|
||||
throw new RuntimeException("rule.drl 中未找到 buildParam 函数结构");
|
||||
}
|
||||
|
||||
int oldMarker = content.indexOf(marker, buildParamStart);
|
||||
int insertFrom;
|
||||
if (oldMarker > 0 && oldMarker < returnPos) {
|
||||
insertFrom = oldMarker;
|
||||
} else {
|
||||
// 首次同步:保留原内容,追加自动生成段
|
||||
insertFrom = returnPos;
|
||||
}
|
||||
return content.substring(0, insertFrom) + generatedLines + "\n" + content.substring(returnPos);
|
||||
}
|
||||
|
||||
private String escapeJavaString(String s) {
|
||||
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
@@ -82,12 +82,13 @@
|
||||
</resultMap>
|
||||
<select id="findComponentsByPlatformId" resultMap="VPlatformComponentMap">
|
||||
SELECT * FROM platform_component
|
||||
WHERE platform_id=#{platformId}
|
||||
WHERE platform_id=#{platformId} AND platform_component.type = "comm"
|
||||
</select>
|
||||
|
||||
<resultMap id="VPBasicPlatformMap" type="com.solution.rule.domain.BasicPlatform">
|
||||
<result property="id" column="id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="behaviortreeId" column="behaviortree_id"/>
|
||||
<result property="description" column="description"/>
|
||||
</resultMap>
|
||||
<select id="findAllBasicPlatformComponents" resultMap="VPBasicPlatformMap">
|
||||
@@ -114,11 +115,19 @@
|
||||
WHERE pc.type = 'comm'
|
||||
AND p.scenario_id = #{scenarioId}
|
||||
ORDER BY p.name,pc.name
|
||||
<!-- SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
p.description
|
||||
FROM platform p
|
||||
LEFT JOIN platform_component pc ON p.id = pc.platform_id
|
||||
WHERE pc.type = 'comm'
|
||||
AND p.scenario_id = #{scenarioId}
|
||||
ORDER BY p.name,pc.name
|
||||
</select>
|
||||
|
||||
<select id="findAllPlatformComponents" resultMap="VPlatformMap">
|
||||
SELECT *
|
||||
FROM platform
|
||||
FROM platform-->
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,222 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.solution.rule.mapper.RuleConfigMapper">
|
||||
|
||||
<resultMap id="RuleConfigMap" type="com.solution.rule.domain.config.RuleConfig">
|
||||
<id property="id" column="id"/>
|
||||
<result property="ruleCode" column="rule_code"/>
|
||||
<result property="ruleName" column="rule_name"/>
|
||||
<result property="levelCode" column="level_code"/>
|
||||
<result property="kindCode" column="kind_code"/>
|
||||
<result property="moduleCode" column="module_code"/>
|
||||
<result property="priorityNo" column="priority_no"/>
|
||||
<result property="conditionExpr" column="condition_expr"/>
|
||||
<result property="actionExpr" column="action_expr"/>
|
||||
<result property="versionNo" column="version_no"/>
|
||||
<result property="enabled" column="enabled"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
<result property="updatedAt" column="updated_at"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="RuleConfigParamMap" type="com.solution.rule.domain.config.RuleConfigParam">
|
||||
<result property="ruleCode" column="rule_code"/>
|
||||
<result property="paramKey" column="param_key"/>
|
||||
<result property="paramVal" column="param_val"/>
|
||||
<result property="valType" column="val_type"/>
|
||||
<result property="paramName" column="param_name"/>
|
||||
<result property="sortNo" column="sort_no"/>
|
||||
<result property="enabled" column="enabled"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="RuleDictItemMap" type="com.solution.rule.domain.config.RuleDictItem">
|
||||
<result property="dictType" column="dict_type"/>
|
||||
<result property="dictCode" column="dict_code"/>
|
||||
<result property="dictName" column="dict_name"/>
|
||||
<result property="sortNo" column="sort_no"/>
|
||||
<result property="enabled" column="enabled"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="RuleConfigTaskTypeRowMap" type="com.solution.rule.domain.config.RuleConfigTaskTypeRow">
|
||||
<result property="ruleCode" column="rule_code"/>
|
||||
<result property="taskTypeCode" column="task_type_code"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectRuleConfigList" resultMap="RuleConfigMap">
|
||||
SELECT id, rule_code, rule_name, level_code, kind_code, module_code, priority_no,
|
||||
condition_expr, action_expr, version_no, enabled, remark, created_at, updated_at
|
||||
FROM rule_item
|
||||
<where>
|
||||
<if test="ruleCode != null and ruleCode != ''">
|
||||
AND rule_code = #{ruleCode}
|
||||
</if>
|
||||
<if test="ruleName != null and ruleName != ''">
|
||||
AND rule_name LIKE CONCAT('%', #{ruleName}, '%')
|
||||
</if>
|
||||
<if test="levelCode != null and levelCode != ''">
|
||||
AND level_code = #{levelCode}
|
||||
</if>
|
||||
<if test="kindCode != null and kindCode != ''">
|
||||
AND kind_code = #{kindCode}
|
||||
</if>
|
||||
<if test="moduleCode != null and moduleCode != ''">
|
||||
AND module_code = #{moduleCode}
|
||||
</if>
|
||||
<if test="enabled != null">
|
||||
AND enabled = #{enabled}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY priority_no ASC, updated_at DESC
|
||||
</select>
|
||||
|
||||
<select id="selectRuleConfigByCode" resultMap="RuleConfigMap">
|
||||
SELECT id, rule_code, rule_name, level_code, kind_code, module_code, priority_no,
|
||||
condition_expr, action_expr, version_no, enabled, remark, created_at, updated_at
|
||||
FROM rule_item
|
||||
WHERE rule_code = #{ruleCode}
|
||||
</select>
|
||||
|
||||
<select id="countByRuleCode" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM rule_item
|
||||
WHERE rule_code = #{ruleCode}
|
||||
</select>
|
||||
|
||||
<insert id="insertRuleConfig" parameterType="com.solution.rule.domain.config.RuleConfig">
|
||||
INSERT INTO rule_item
|
||||
(rule_code, rule_name, level_code, kind_code, module_code, priority_no, condition_expr,
|
||||
action_expr, version_no, enabled, remark, created_at, updated_at)
|
||||
VALUES
|
||||
(#{ruleCode}, #{ruleName}, #{levelCode}, #{kindCode}, #{moduleCode}, #{priorityNo}, #{conditionExpr},
|
||||
#{actionExpr}, #{versionNo}, #{enabled}, #{remark}, NOW(), NOW())
|
||||
</insert>
|
||||
|
||||
<update id="updateRuleConfig" parameterType="com.solution.rule.domain.config.RuleConfig">
|
||||
UPDATE rule_item
|
||||
<set>
|
||||
<if test="ruleName != null">rule_name = #{ruleName},</if>
|
||||
<if test="levelCode != null">level_code = #{levelCode},</if>
|
||||
<if test="kindCode != null">kind_code = #{kindCode},</if>
|
||||
<if test="moduleCode != null">module_code = #{moduleCode},</if>
|
||||
<if test="priorityNo != null">priority_no = #{priorityNo},</if>
|
||||
<if test="conditionExpr != null">condition_expr = #{conditionExpr},</if>
|
||||
<if test="actionExpr != null">action_expr = #{actionExpr},</if>
|
||||
<if test="versionNo != null">version_no = #{versionNo},</if>
|
||||
<if test="enabled != null">enabled = #{enabled},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
updated_at = NOW()
|
||||
</set>
|
||||
WHERE rule_code = #{ruleCode}
|
||||
</update>
|
||||
|
||||
<delete id="deleteRuleConfigByCodes">
|
||||
DELETE FROM rule_item
|
||||
WHERE rule_code IN
|
||||
<foreach item="code" collection="ruleCodes" open="(" separator="," close=")">
|
||||
#{code}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="selectParamsByRuleCode" resultMap="RuleConfigParamMap">
|
||||
SELECT rule_code, param_key, param_val, val_type, param_name, sort_no, enabled, remark
|
||||
FROM rule_item_param
|
||||
WHERE rule_code = #{ruleCode}
|
||||
ORDER BY sort_no ASC, id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectParamsByRuleCodes" resultMap="RuleConfigParamMap">
|
||||
SELECT rule_code, param_key, param_val, val_type, param_name, sort_no, enabled, remark
|
||||
FROM rule_item_param
|
||||
<where>
|
||||
<if test="ruleCodes != null and ruleCodes.size() > 0">
|
||||
rule_code IN
|
||||
<foreach collection="ruleCodes" item="c" open="(" separator="," close=")">
|
||||
#{c}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="ruleCodes == null or ruleCodes.size() == 0">
|
||||
1 = 0
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY rule_code ASC, sort_no ASC, id ASC
|
||||
</select>
|
||||
|
||||
<delete id="deleteParamsByRuleCodes">
|
||||
DELETE FROM rule_item_param
|
||||
WHERE rule_code IN
|
||||
<foreach item="code" collection="ruleCodes" open="(" separator="," close=")">
|
||||
#{code}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<insert id="insertParamsBatch">
|
||||
INSERT INTO rule_item_param
|
||||
(rule_code, param_key, param_val, val_type, param_name, sort_no, enabled, remark, created_at, updated_at)
|
||||
VALUES
|
||||
<foreach item="item" collection="params" separator=",">
|
||||
(#{item.ruleCode}, #{item.paramKey}, #{item.paramVal}, #{item.valType}, #{item.paramName},
|
||||
#{item.sortNo}, #{item.enabled}, #{item.remark}, NOW(), NOW())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="selectTaskTypesByRuleCode" resultType="string">
|
||||
SELECT task_type_code
|
||||
FROM rule_item_task_type
|
||||
WHERE rule_code = #{ruleCode}
|
||||
ORDER BY id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectTaskTypesByRuleCodes" resultMap="RuleConfigTaskTypeRowMap">
|
||||
SELECT rule_code, task_type_code
|
||||
FROM rule_item_task_type
|
||||
<where>
|
||||
<if test="ruleCodes != null and ruleCodes.size() > 0">
|
||||
rule_code IN
|
||||
<foreach collection="ruleCodes" item="c" open="(" separator="," close=")">
|
||||
#{c}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="ruleCodes == null or ruleCodes.size() == 0">
|
||||
1 = 0
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY rule_code ASC, id ASC
|
||||
</select>
|
||||
|
||||
<delete id="deleteTaskTypesByRuleCodes">
|
||||
DELETE FROM rule_item_task_type
|
||||
WHERE rule_code IN
|
||||
<foreach item="code" collection="ruleCodes" open="(" separator="," close=")">
|
||||
#{code}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<insert id="insertTaskTypesBatch">
|
||||
INSERT INTO rule_item_task_type (rule_code, task_type_code, created_at)
|
||||
VALUES
|
||||
<foreach item="taskType" collection="taskTypes" separator=",">
|
||||
(#{ruleCode}, #{taskType}, NOW())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="selectDictByType" resultMap="RuleDictItemMap">
|
||||
SELECT dict_type, dict_code, dict_name, sort_no, enabled, remark
|
||||
FROM rule_dict
|
||||
WHERE dict_type = #{dictType}
|
||||
ORDER BY sort_no ASC, id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectEnabledParamsForGlobal" resultMap="RuleConfigParamMap">
|
||||
SELECT p.rule_code, p.param_key, p.param_val, p.val_type, p.param_name, p.sort_no, p.enabled, p.remark
|
||||
FROM rule_item_param p
|
||||
INNER JOIN rule_item r ON p.rule_code = r.rule_code
|
||||
WHERE r.enabled = 1
|
||||
AND p.enabled = 1
|
||||
ORDER BY r.priority_no ASC, p.sort_no ASC, p.id ASC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -79,7 +79,7 @@ function Map buildParam(){
|
||||
param.put("missileScore", 1);
|
||||
|
||||
// ===================== 目标分配参数(写入 Tasks.task.execute) =====================
|
||||
// executeTypeDefault:生成 execute[0] 的类型字段
|
||||
// executeTypeDefault:生成 execute[0] 的类型字段 取值:strike_test/assault
|
||||
param.put("executeTypeDefault", "assault");
|
||||
// targetPickMode:roundRobin(稳定轮询) / random(伪随机但同输入稳定)
|
||||
param.put("targetPickMode", "roundRobin");
|
||||
@@ -200,16 +200,111 @@ function Map buildParam(){
|
||||
param.put("wingmanAltBaseMeters", 40);
|
||||
param.put("wingmanAltScale", 1.0);
|
||||
|
||||
// ===== 以下由规则配置表自动同步生成(请勿手改 param.put 段) =====
|
||||
param.put("groupRuleEnabled", true);
|
||||
param.put("groupDrawNameSuffix", "编组");
|
||||
param.put("groupDrawNameWithIndex", false);
|
||||
param.put("groupFormationMode", "onePerRed");
|
||||
param.put("groupClusterSize", 3);
|
||||
param.put("groupLeaderPickMode", "byHitRateThenId");
|
||||
param.put("groupMinMembersForWingman", 2);
|
||||
param.put("wingmanDistanceBaseMeters", 100);
|
||||
param.put("wingmanDistanceStepMeters", 50);
|
||||
param.put("wingmanAngleBaseDeg", 50);
|
||||
param.put("wingmanAngleStepDeg", 15);
|
||||
param.put("wingmanAltBaseMeters", 40);
|
||||
param.put("wingmanAltScale", 1.0);
|
||||
param.put("enableTrackWarZoneClamp", true);
|
||||
param.put("trackRuleEnabled", true);
|
||||
param.put("trackRouteAlgorithm", "followBlue");
|
||||
param.put("trackRouteNameSuffix", "航迹");
|
||||
param.put("trackAirDataTypeCsv", "taskPlane,air,plane,flight");
|
||||
param.put("trackAirKeywordsCsv", "机,飞,空,J-,F-,无人机,直升机");
|
||||
param.put("trackGroundTrackType", "routeLineGround");
|
||||
param.put("trackFallbackBearingDeg", 0);
|
||||
param.put("trackExtraNodesMax", 0);
|
||||
param.put("trackShortPathSegments", 3);
|
||||
param.put("trackFlankOffsetMeters", 800);
|
||||
param.put("trackFlankSideMode", "alternate");
|
||||
param.put("trackJamWobbleMeters", 400);
|
||||
param.put("trackJamSegments", 4);
|
||||
param.put("enableWarZoneClamp", true);
|
||||
param.put("positionRuleEnabled", true);
|
||||
param.put("positionAnchorMode", "hybrid");
|
||||
param.put("trackPointDirectionMode", "head2next");
|
||||
param.put("fallbackBearingDeg", 0);
|
||||
param.put("deployDistanceKmMin", 8);
|
||||
param.put("deployDistanceKmMax", 30);
|
||||
param.put("deployDistanceKmDefault", 15);
|
||||
param.put("distanceByPlatformCsv", "");
|
||||
param.put("formationType", "line");
|
||||
param.put("formationSpacingMeters", 300);
|
||||
param.put("formationHeadingOffsetDeg", 15);
|
||||
param.put("defaultDeployHeight", 30);
|
||||
param.put("heightFollowBlueRatio", 0.0);
|
||||
param.put("warZoneClampMode", "nearestInside");
|
||||
param.put("minInterPlatformDistanceMeters", 80);
|
||||
param.put("redHitRateThreshold", 0.6);
|
||||
param.put("maxExtraWeaponsPerTask", 2);
|
||||
param.put("maxSupplementRounds", 2);
|
||||
param.put("extraPickMinScore", 1);
|
||||
param.put("executeTypeDefault", "assault");
|
||||
param.put("targetPickMode", "roundRobin");
|
||||
param.put("minTargetsPerRed", 1);
|
||||
param.put("maxTargetsPerRedCap", 3);
|
||||
param.put("radToTargetsCsv", "0.8:1,0.5:2,0.2:3");
|
||||
param.put("rangeParseRegex", "(\\\\d+(?:\\\\.\\\\d+)?)");
|
||||
param.put("rangeUnit", "km");
|
||||
param.put("minRangeToAllowAssignKm", 0);
|
||||
param.put("weight", 1);
|
||||
param.put("minSelectedScore", 1);
|
||||
param.put("tieBreak", "equipmentId");
|
||||
param.put("outputDrawNameSuffix", "打击任务");
|
||||
param.put("ruleSlotCount", 3);
|
||||
param.put("blueRuleKeywords_1", "F-16,F-35");
|
||||
param.put("redRuleKeywords_1", "防空,导弹,无人机");
|
||||
param.put("ruleScore_1", 5);
|
||||
param.put("blueRuleKeywords_2", "坦克,装甲");
|
||||
param.put("redRuleKeywords_2", "反坦克");
|
||||
param.put("ruleScore_2", 4);
|
||||
param.put("blueRuleKeywords_3", "地面,突击");
|
||||
param.put("redRuleKeywords_3", "远火,榴弹,炮");
|
||||
param.put("ruleScore_3", 2);
|
||||
param.put("bluePlatformKeywords_air", "F-16,J-10,F-35");
|
||||
param.put("redPreferredWhenBlueAir", "防空,导弹,无人机,直升机,空空");
|
||||
param.put("airScore", 2);
|
||||
param.put("airTaskKeywords", "空中,制空,拦截,空战");
|
||||
param.put("airTaskScore", 10);
|
||||
param.put("groundTaskKeywords", "地面,突击,登陆");
|
||||
param.put("redPreferredWhenGround", "远火,榴弹,炮,火箭");
|
||||
param.put("groundScore", 1);
|
||||
param.put("tankKeywords", "坦克,装甲");
|
||||
param.put("redMatchKeywords_tank", "反坦克");
|
||||
param.put("tankScore", 1);
|
||||
param.put("missileKeywords", "导弹,火箭弹,巡航");
|
||||
param.put("redMatchKeywords_missile", "防空,导弹,导弹发射");
|
||||
param.put("missileScore", 1);
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
function void mergeDefaultParams(Map current){
|
||||
Map defaults = buildParam();
|
||||
for (Object k : defaults.keySet()) {
|
||||
if (!current.containsKey(k)) {
|
||||
current.put(k, defaults.get(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rule "装备匹配"
|
||||
salience 100
|
||||
when
|
||||
$fact : DroolsFact(task != null)
|
||||
then
|
||||
// 以本文件 buildParam 为真源覆盖同名键,再执行 Java 侧匹配逻辑
|
||||
globalParams.putAll(buildParam());
|
||||
// globalParams.putAll(buildParam());
|
||||
mergeDefaultParams(globalParams);
|
||||
equipmentRule($fact, globalParams);
|
||||
end
|
||||
|
||||
@@ -219,7 +314,8 @@ when
|
||||
$fact : DroolsFact(task != null)
|
||||
then
|
||||
// 显式目标分配规则:填充 Tasks.task.execute.targetList[*].targetId
|
||||
globalParams.putAll(buildParam());
|
||||
// globalParams.putAll(buildParam());
|
||||
mergeDefaultParams(globalParams);
|
||||
target($fact, globalParams);
|
||||
end
|
||||
|
||||
@@ -229,7 +325,8 @@ when
|
||||
$fact : DroolsFact(task != null)
|
||||
then
|
||||
// 显式阵位规则:填充 redWeapons.SubComponents.platform[].positions
|
||||
globalParams.putAll(buildParam());
|
||||
// globalParams.putAll(buildParam());
|
||||
mergeDefaultParams(globalParams);
|
||||
position($fact, globalParams);
|
||||
end
|
||||
|
||||
@@ -239,7 +336,8 @@ when
|
||||
$fact : DroolsFact(task != null)
|
||||
then
|
||||
// 显式航迹规则:填充 TrackParam 下各航迹 id,并绑定 execute[0].targetList[*].moveRouteId
|
||||
globalParams.putAll(buildParam());
|
||||
// globalParams.putAll(buildParam());
|
||||
mergeDefaultParams(globalParams);
|
||||
trackRoute($fact, globalParams);
|
||||
end
|
||||
|
||||
@@ -249,6 +347,7 @@ when
|
||||
$fact : DroolsFact(task != null)
|
||||
then
|
||||
// 显式编组规则:填充 TrackParam.Groups(groupType=addGroup)与 wingmanData
|
||||
globalParams.putAll(buildParam());
|
||||
// globalParams.putAll(buildParam());
|
||||
mergeDefaultParams(globalParams);
|
||||
groupFormation($fact, globalParams);
|
||||
end
|
||||
|
||||
@@ -182,7 +182,7 @@ ON DUPLICATE KEY UPDATE
|
||||
`remark` = VALUES(`remark`);
|
||||
|
||||
-- 4) 规则适用任务类型(默认全部规则覆盖五类任务,后续可在前端按需调整)
|
||||
INSERT INTO `rule_item_task_type` (`rule_code`, `task_type_code`)
|
||||
INSERT IGNORE INTO `rule_item_task_type` (`rule_code`, `task_type_code`)
|
||||
SELECT r.rule_code, t.task_type_code
|
||||
FROM (
|
||||
SELECT 'R_TASK_SELECT_BASE' AS rule_code
|
||||
@@ -208,6 +208,4 @@ CROSS JOIN (
|
||||
UNION ALL SELECT 'intercept'
|
||||
UNION ALL SELECT 'support'
|
||||
UNION ALL SELECT 'jamming'
|
||||
) t
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`task_type_code` = VALUES(`task_type_code`);
|
||||
) t;
|
||||
|
||||
204
auto-solution-rule/src/main/resources/task/data.json
Normal file
204
auto-solution-rule/src/main/resources/task/data.json
Normal file
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"assign_target": [
|
||||
{
|
||||
"task": "task1",
|
||||
"type": "fire",
|
||||
"target": [
|
||||
"enemy_p1",
|
||||
"enemyp2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"task": "task2",
|
||||
"type": "fire",
|
||||
"target": [
|
||||
"enemy_p1333",
|
||||
"enemyp2333"
|
||||
]
|
||||
},
|
||||
{
|
||||
"task": "task3",
|
||||
"type": "radar",
|
||||
"target": [
|
||||
"enemy_p1444",
|
||||
"enemyp2444"
|
||||
]
|
||||
}
|
||||
],
|
||||
"task": [
|
||||
{
|
||||
"task_name": "task1",
|
||||
"platform": [
|
||||
{
|
||||
"name": "platform1",
|
||||
"platform_type": "WSF_PLATFORM",
|
||||
"longitude": "120:49:24.79e",
|
||||
"latitude": "23:47:26.60n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:49:24.79e",
|
||||
"latitude": "23:47:26.60n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:49:42.46e",
|
||||
"latitude": "23:47:42.80n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:50:19.01e",
|
||||
"latitude": "23:47:51.84n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "platform2",
|
||||
"platform_type": "WSF_PLATFORM",
|
||||
"longitude": "120:50:19.01e",
|
||||
"latitude": "23:47:51.84n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:50:19.01e",
|
||||
"latitude": "23:47:51.84n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"begin": 50,
|
||||
"end": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"task_name": "task2",
|
||||
"platform": [
|
||||
{
|
||||
"name": "platform3",
|
||||
"platform_type": "SENSOR_TYPE",
|
||||
"longitude": "120:50:19.01e",
|
||||
"latitude": "23:47:51.84n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:50:19.01e",
|
||||
"latitude": "23:47:51.84n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
},
|
||||
{
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "platform5",
|
||||
"platform_type": "SENSOR_TYPE",
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:50:42.73e",
|
||||
"latitude": "23:48:09.86n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "platform4",
|
||||
"platform_type": "JAM_TYPE",
|
||||
"longitude": "120:49:55.28e",
|
||||
"latitude": "23:48:38.68n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:49:55.28e",
|
||||
"latitude": "23:48:38.68n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "platform9",
|
||||
"platform_type": "JAM_TYPE",
|
||||
"longitude": "120:49:55.28e",
|
||||
"latitude": "23:48:38.68n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:49:55.28e",
|
||||
"latitude": "23:48:38.68n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"begin": 110,
|
||||
"end": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"task_name": "task3",
|
||||
"platform": [
|
||||
{
|
||||
"name": "platform6",
|
||||
"platform_type": "WSF_PLATFORM",
|
||||
"longitude": "120:49:45.11e",
|
||||
"latitude": "23:48:53.09n",
|
||||
"altitude": 0,
|
||||
"route": [
|
||||
{
|
||||
"longitude": "120:49:45.11e",
|
||||
"latitude": "23:48:53.09n",
|
||||
"altitude": 0,
|
||||
"speed": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"begin": 50,
|
||||
"end": 300
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
6485
auto-solution-rule/src/main/resources/task/kj-3.json
Normal file
6485
auto-solution-rule/src/main/resources/task/kj-3.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
FROM behaviortree
|
||||
WHERE scenario_id=#{id}
|
||||
</select>
|
||||
<select id="selectAllRelationBySceneId" resultType="com.solution.scene.domain.PlatformCommunication"
|
||||
|
||||
<resultMap id="PlatformCommunicationResultMap" type="com.solution.scene.domain.PlatformCommunication">
|
||||
<id property="id" column="id"/>
|
||||
<result property="commandPlatform" column="command_platform"/>
|
||||
<result property="subordinatePlatform" column="subordinate_platform"/>
|
||||
<result property="commandComm" column="command_comm"/>
|
||||
<result property="subordinateComm" column="subordinate_comm"/>
|
||||
<result property="scenaryId" column="scenary_id"/>
|
||||
</resultMap>
|
||||
<select id="selectAllRelationBySceneId" resultMap="PlatformCommunicationResultMap"
|
||||
parameterType="java.lang.Integer">
|
||||
SELECT id,command_platform,subordinate_platform,command_comm,subordinate_comm,scenary_id
|
||||
FROM platform_communication
|
||||
|
||||
880
modeler/package-lock.json
generated
880
modeler/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g6": "^5.0.49",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@antv/x6": "^3.1.2",
|
||||
"@antv/x6-vue-shape": "^3.0.2",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
|
||||
@@ -58,4 +58,12 @@ export const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
component: () => import('@/views/decision/rule/management.vue'),
|
||||
},
|
||||
{
|
||||
name: 'decision-rule-config',
|
||||
path: '/app/decision/rule-config',
|
||||
meta: {
|
||||
title: '规则聚合测试',
|
||||
},
|
||||
component: () => import('@/views/decision/rule-config/management.vue'),
|
||||
},
|
||||
]
|
||||
@@ -853,6 +853,7 @@
|
||||
.ks-model-builder-content,
|
||||
.ks-model-builder-right {
|
||||
height: calc(100vh - 90px);
|
||||
overflow: auto;
|
||||
|
||||
.ant-card-body {
|
||||
padding: 6px;
|
||||
@@ -987,6 +988,7 @@
|
||||
|
||||
.ant-collapse-content-box {
|
||||
max-height: 37vh;
|
||||
height: fit-content;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
@@ -1555,6 +1557,44 @@
|
||||
border: 1px solid #0f4a7c;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
// 修复 mini 模式分页滚动条问题
|
||||
&.ant-pagination-mini {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
|
||||
.ant-pagination-simple-pager {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
|
||||
input {
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
padding: 0 4px;
|
||||
margin: 0 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination-total-text {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.ant-pagination-prev,
|
||||
.ant-pagination-next,
|
||||
.ant-pagination-jump-prev,
|
||||
.ant-pagination-jump-next {
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
|
||||
.ant-pagination-item-link {
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1750,6 +1790,23 @@
|
||||
border: 1px solid #475f71;
|
||||
border-radius: 2px;
|
||||
|
||||
.ks-sidebar-list-param-head {
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(71, 95, 113, 0.65);
|
||||
}
|
||||
|
||||
.ks-sidebar-list-param-head-cell {
|
||||
font-size: 12px;
|
||||
color: #8fc7de;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ks-sidebar-list-param-head-cell--actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ks-sidebar-list-param-item {
|
||||
margin-bottom: 15px;
|
||||
|
||||
@@ -1877,3 +1934,231 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* rule-config 页:侧栏与主内容区视觉统一(仅带 .rule-config-sidebar 时生效) */
|
||||
.rule-config-sidebar.ks-layout-sidebar {
|
||||
background-color: #020a14;
|
||||
background-image:
|
||||
linear-gradient(180deg, rgba(2, 10, 20, 0.94) 0%, rgba(2, 12, 24, 0.9) 50%, rgba(1, 8, 18, 0.92) 100%),
|
||||
url('@/assets/icons/bg-fk.png');
|
||||
background-size: 100% 100%, 100% 100%;
|
||||
background-position: center, center;
|
||||
border-inline-end: 1px solid rgba(55, 126, 173, 0.35);
|
||||
|
||||
.ks-sidebar-header {
|
||||
background: url('@/assets/icons/bg-fk-title.png') center / 100% 100%;
|
||||
min-height: 104px;
|
||||
padding: 8px 10px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.ant-tree {
|
||||
background: transparent;
|
||||
color: #eee;
|
||||
|
||||
.ant-tree-node-content-wrapper {
|
||||
color: #eee;
|
||||
|
||||
&:hover {
|
||||
background: rgba(9, 38, 75, 0.55);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree-node-content-wrapper.ant-tree-node-selected {
|
||||
background: rgba(17, 55, 126, 0.65);
|
||||
}
|
||||
|
||||
.ant-tree-switcher {
|
||||
color: #a2b1ba;
|
||||
}
|
||||
|
||||
.ant-tree-title {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination {
|
||||
color: #a2b1ba;
|
||||
|
||||
.ant-pagination-item-link,
|
||||
.ant-pagination-item a {
|
||||
color: #a2b1ba;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rule-config-main-split {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
padding-right: 0;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(1, 12, 28, 0.94) 0%, rgba(2, 16, 36, 0.92) 100%),
|
||||
url('@/assets/icons/page-card-body.png') center / 100% 100%;
|
||||
}
|
||||
|
||||
.rule-config-graph-placeholder {
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed rgba(55, 126, 173, 0.45);
|
||||
margin: 8px 0 8px 8px;
|
||||
border-radius: 2px;
|
||||
color: #a2b1ba;
|
||||
background: rgba(8, 29, 54, 0.25);
|
||||
}
|
||||
|
||||
.rule-config-graph-placeholder--chart {
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.rule-config-main-split-resizer {
|
||||
flex: 0 0 6px;
|
||||
width: 6px;
|
||||
margin: 8px 0;
|
||||
cursor: col-resize;
|
||||
flex-shrink: 0;
|
||||
border-radius: 2px;
|
||||
background: rgba(55, 126, 173, 0.25);
|
||||
|
||||
&:hover {
|
||||
background: rgba(55, 126, 173, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.rule-config-sidebar .ks-sidebar-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(71, 95, 113, 0.78);
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(180deg, rgba(8, 29, 54, 0.88) 0%, rgba(6, 21, 40, 0.92) 100%);
|
||||
box-shadow: inset 0 0 0 1px rgba(18, 70, 120, 0.18);
|
||||
}
|
||||
|
||||
.rule-config-sidebar .ks-sidebar-add {
|
||||
position: static;
|
||||
right: auto;
|
||||
top: auto;
|
||||
font-size: 12px;
|
||||
|
||||
.anticon {
|
||||
display: block;
|
||||
float: left;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-config-sidebar-action.ant-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 62px;
|
||||
padding-inline: 10px;
|
||||
border-color: #1c468b;
|
||||
background: linear-gradient(0deg, #14223b 0, #1c468b 100%);
|
||||
color: #eee;
|
||||
|
||||
.anticon {
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover,
|
||||
&:hover,
|
||||
&:active {
|
||||
border-color: #166094;
|
||||
background: linear-gradient(90deg, #3687bc 0%, #074375 100%);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-config-right-cluster {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.rule-config-graph-placeholder__text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rule-config-panel-toggle {
|
||||
flex: 0 0 20px;
|
||||
width: 20px;
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 2px 0 0 2px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #eee;
|
||||
background: rgba(9, 38, 75, 0.75);
|
||||
|
||||
&:hover {
|
||||
background: rgba(17, 55, 126, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
.rule-config-right-panel {
|
||||
position: relative;
|
||||
flex: 0 0 min(480px, 42vw);
|
||||
width: min(480px, 42vw);
|
||||
min-width: 320px;
|
||||
max-width: 55%;
|
||||
transition: flex-basis 0.2s ease, width 0.2s ease, opacity 0.2s ease, min-width 0.2s ease;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid rgba(71, 95, 113, 0.6);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(4, 24, 50, 0.96) 0%, rgba(3, 17, 37, 0.94) 100%);
|
||||
box-shadow: inset 0 0 0 1px rgba(18, 70, 120, 0.18);
|
||||
}
|
||||
|
||||
.rule-config-right-panel--collapsed {
|
||||
flex: 0 0 0;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
border-left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rule-config-right-panel-edge {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
width: 8px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
cursor: col-resize;
|
||||
background: linear-gradient(180deg, rgba(55, 126, 173, 0.15) 0%, rgba(55, 126, 173, 0.5) 50%, rgba(55, 126, 173, 0.15) 100%);
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(180deg, rgba(55, 126, 173, 0.3) 0%, rgba(55, 126, 173, 0.72) 50%, rgba(55, 126, 173, 0.3) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.rule-config-right-panel__inner {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 10px 10px 10px 12px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
*/
|
||||
|
||||
import { HttpRequestClient } from '@/utils/request';
|
||||
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest } from './types';
|
||||
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest, CommunicationRelationsResponse } from './types';
|
||||
import type { PlatformWithComponentsResponse } from '../types';
|
||||
import type { BasicResponse } from '@/types';
|
||||
import type { BehaviorTree } from '../designer/tree';
|
||||
|
||||
const req = HttpRequestClient.create<BasicResponse>({
|
||||
baseURL: '/api',
|
||||
@@ -32,6 +33,29 @@ export const findPlatformWithComponents = (id: number): Promise<PlatformWithComp
|
||||
return req.get<PlatformWithComponentsResponse>(`/system/firerule/platforms/${id}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取场景的所有通信关系
|
||||
* @param id 场景ID
|
||||
* @returns 通信关系列表
|
||||
*/
|
||||
export const findRelations = (id: number): Promise<CommunicationRelationsResponse> => {
|
||||
return req.get<CommunicationRelationsResponse>(`/system/scene/getAllRelation/${id}`);
|
||||
};
|
||||
|
||||
export const saveScenario = (scenario: Scenario): Promise<BasicResponse> => {
|
||||
return req.postJson<BasicResponse>(`/system/scene/saveSceneConfig`,scenario);
|
||||
};
|
||||
|
||||
// 获取场景下的所有行为树列表
|
||||
export const getAllBehaviorTreesBySceneId = (sceneId: number): Promise<{ code: number; msg: string; data: BehaviorTree[] }> => {
|
||||
return req.get<{ code: number; msg: string; data: BehaviorTree[] }>(`/system/scene/getAllTree/${sceneId}`);
|
||||
};
|
||||
|
||||
// 更新平台的行为树id
|
||||
export const updateBehaviorTreeIdOfPlatform = (query: { id: number, behaviortreeId: number}): Promise<BasicResponse> => {
|
||||
return req.putJson<BasicResponse>(`/system/behaviortree/behaviortreeId`, query);
|
||||
};
|
||||
// 更新行为树的平台id
|
||||
export const updateBehaviorTree = (behaviorTree: BehaviorTree): Promise<BasicResponse> => {
|
||||
return req.putJson<BasicResponse>(`/system/behaviortree`, behaviorTree);
|
||||
};
|
||||
@@ -21,6 +21,14 @@
|
||||
<div class="ks-model-builder-content" style="width: calc(100% - 250px);">
|
||||
<div class="ks-model-builder-actions">
|
||||
<a-space>
|
||||
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" style="width: auto;" size="small" @click="handleGenerateRandom">
|
||||
<ThunderboltOutlined />
|
||||
<span>随机生成</span>
|
||||
</a-button>
|
||||
<a-button v-if="graph && currentScenario && currentScenario.id > 0" class="ks-model-builder-save" style="width: auto;" size="small" @click="handleLoadFromBackend">
|
||||
<DatabaseOutlined />
|
||||
<span>从后端加载</span>
|
||||
</a-button>
|
||||
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" size="small" @click="handleSave">
|
||||
<CheckOutlined />
|
||||
<span>保存</span>
|
||||
@@ -50,22 +58,26 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getTeleport } from '@antv/x6-vue-shape';
|
||||
import { Graph, Node, type NodeProperties } from '@antv/x6';
|
||||
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
|
||||
import { CheckCircleOutlined, CheckOutlined, DatabaseOutlined, RollbackOutlined, SaveOutlined, ThunderboltOutlined } from '@ant-design/icons-vue';
|
||||
import { Wrapper } from '@/components/wrapper';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
import Header from '../header.vue';
|
||||
|
||||
import type { Scenario } from './types';
|
||||
import type { PlatformWithComponents } from '../types';
|
||||
import { createLineOptions, type GraphContainer, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
|
||||
import { createLineOptions, type GraphContainer, type GraphEdgeElement, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
|
||||
|
||||
import { registerScenarioElement } from './register';
|
||||
import { createGraphScenarioElement, createGraphTaskElementFromScenario } from './utils';
|
||||
|
||||
import PlatformCard from './platform-card.vue';
|
||||
import NodesCard from './nodes-card.vue';
|
||||
import { findOneScenarioById, saveScenario } from './api';
|
||||
import { findOneScenarioById, saveScenario, findRelations, getAllBehaviorTreesBySceneId, findPlatformWithComponents } from './api';
|
||||
import { resolveConnectionRelation } from './relation';
|
||||
import { generateRandomCommunicationData } from '../graph/random-data-generator';
|
||||
import { convertRecordsToGraphContainer, type CommunicationRecord } from '../graph/data-converter';
|
||||
import { log } from 'echarts/types/src/util/log.js';
|
||||
import { router } from '@/router';
|
||||
|
||||
const TeleportContainer = defineComponent(getTeleport());
|
||||
|
||||
@@ -81,6 +93,8 @@ export default defineComponent({
|
||||
CheckCircleOutlined,
|
||||
CheckOutlined,
|
||||
RollbackOutlined,
|
||||
ThunderboltOutlined,
|
||||
DatabaseOutlined,
|
||||
TeleportContainer,
|
||||
},
|
||||
setup() {
|
||||
@@ -92,7 +106,6 @@ export default defineComponent({
|
||||
const isDraggingOver = ref(false);
|
||||
const currentScenarioEditing = ref<boolean>(false);
|
||||
const currentScenario = ref<Scenario | null>(null);
|
||||
const currentGraph = ref<GraphContainer | null>(null);
|
||||
const selectedModelNode = ref<Node<NodeProperties> | null>(null);
|
||||
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
|
||||
const changed = ref<boolean>(false);
|
||||
@@ -211,25 +224,132 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (scenario: Scenario) => {
|
||||
const handleSelect = async (scenario: Scenario) => {
|
||||
let nodeGraph: GraphContainer | null = null;
|
||||
try {
|
||||
nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer;
|
||||
} catch (e: any) {
|
||||
console.error('parse error,cause:', e);
|
||||
}
|
||||
if (!nodeGraph) {
|
||||
nodeGraph = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 设置当前场景
|
||||
currentScenario.value = {
|
||||
...scenario,
|
||||
graph: nodeGraph,
|
||||
graph: nodeGraph || { nodes: [], edges: [] },
|
||||
relations: []
|
||||
};
|
||||
console.log('选中场景:', currentScenario.value);
|
||||
|
||||
currentScenarioEditing.value = true;
|
||||
|
||||
// 并行加载通信关系和行为树列表
|
||||
if (scenario.id > 0) {
|
||||
try {
|
||||
// 1. 加载通信关系(如果没有已保存的图数据)
|
||||
if (!nodeGraph) {
|
||||
message.loading({ content: '正在加载通信关系...', key: 'loading-relations' });
|
||||
const response = await findRelations(scenario.id);
|
||||
|
||||
console.log('API完整响应:', response);
|
||||
|
||||
// 解析API响应(支持多种格式)
|
||||
let relations: any[] = [];
|
||||
if (Array.isArray(response.data)) {
|
||||
relations = response.data;
|
||||
} else if (response.data && Array.isArray((response.data as any).data)) {
|
||||
relations = (response.data as any).data;
|
||||
} else if (response.data && Array.isArray((response.data as any).rows)) {
|
||||
relations = (response.data as any).rows;
|
||||
} else if (response.data && Array.isArray((response.data as any).list)) {
|
||||
relations = (response.data as any).list;
|
||||
}
|
||||
|
||||
console.log('解析后的通信关系数量:', relations.length);
|
||||
|
||||
if (relations.length > 0) {
|
||||
// 字段名标准化(驼峰转下划线)
|
||||
const normalizedRelations = relations.map((item: any) => ({
|
||||
id: item.id,
|
||||
command_platform: item.commandPlatform || item.command_platform,
|
||||
subordinate_platform: item.subordinatePlatform || item.subordinate_platform,
|
||||
command_comm: item.commandComm || item.command_comm,
|
||||
subordinate_comm: item.subordinateComm || item.subordinate_comm,
|
||||
scenary_id: item.scenaryId || item.scenary_id,
|
||||
}));
|
||||
|
||||
console.log('标准化后的第一条记录:', normalizedRelations[0]);
|
||||
|
||||
// 获取平台列表(包含完整的组件信息)
|
||||
message.loading({ content: '正在获取平台信息...', key: 'loading-platforms' });
|
||||
const platformResponse = await findPlatformWithComponents(currentScenario.value.id);
|
||||
|
||||
if (!platformResponse.data || platformResponse.data.length === 0) {
|
||||
console.warn('未能获取平台列表,将使用简化的组件信息');
|
||||
message.destroy('loading-platforms');
|
||||
|
||||
// 即使没有平台数据也继续转换(传入场景ID)
|
||||
const convertedGraph = convertRecordsToGraphContainer(
|
||||
normalizedRelations,
|
||||
undefined,
|
||||
undefined,
|
||||
currentScenario.value.id
|
||||
);
|
||||
console.log('转换后的图数据:', convertedGraph);
|
||||
|
||||
currentScenario.value.graph = convertedGraph;
|
||||
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
|
||||
|
||||
message.success({ content: `成功加载 ${normalizedRelations.length} 条通信关系(无详细组件信息)`, key: 'loading-relations' });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`获取到 ${platformResponse.data.length} 个平台的完整数据`);
|
||||
platformResponse.data.forEach(platform => {
|
||||
console.log(` 平台 "${platform.name}" (ID=${platform.id}) 有 ${platform.components?.length || 0} 个组件:`,
|
||||
platform.components?.map(c => c.name));
|
||||
});
|
||||
|
||||
// 转换为图数据(传入完整的平台数据数组和场景ID)
|
||||
const convertedGraph = convertRecordsToGraphContainer(
|
||||
normalizedRelations,
|
||||
undefined,
|
||||
platformResponse.data,
|
||||
currentScenario.value.id
|
||||
);
|
||||
|
||||
// 验证转换后的节点数据结构
|
||||
console.log('转换后的图数据:', convertedGraph);
|
||||
|
||||
// 更新当前场景的图数据
|
||||
currentScenario.value.graph = convertedGraph;
|
||||
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
|
||||
|
||||
message.success({ content: `成功加载 ${normalizedRelations.length} 条通信关系`, key: 'loading-relations' });
|
||||
} else {
|
||||
message.warning({ content: '该场景暂无通信关系数据', key: 'loading-relations' });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 加载行为树列表并缓存到graph对象
|
||||
const treesResponse = await getAllBehaviorTreesBySceneId(scenario.id);
|
||||
if (treesResponse.code === 200 && treesResponse.data) {
|
||||
console.log('[communication] 行为树列表加载完成:', treesResponse.data.length, '个');
|
||||
// 将行为树列表存储到graph对象中供node.vue使用
|
||||
if (graph.value) {
|
||||
(graph.value as any).behaviorTrees = treesResponse.data;
|
||||
}
|
||||
} else {
|
||||
console.warn('[communication] 行为树列表加载失败或为空');
|
||||
if (graph.value) {
|
||||
(graph.value as any).behaviorTrees = [];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从后端加载数据失败:', error);
|
||||
message.error({ content: '加载数据失败', key: 'loading-relations' });
|
||||
}
|
||||
}
|
||||
|
||||
createElements();
|
||||
};
|
||||
|
||||
@@ -261,6 +381,7 @@ export default defineComponent({
|
||||
}, 100); // 延迟一会儿,免得连线错位
|
||||
}
|
||||
}
|
||||
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
@@ -277,10 +398,12 @@ export default defineComponent({
|
||||
nodes: [],
|
||||
},
|
||||
};
|
||||
currentGraph.value = {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
};
|
||||
|
||||
// 清空graph中的场景信息
|
||||
if (graph.value) {
|
||||
(graph.value as any).currentScenario = null;
|
||||
}
|
||||
|
||||
selectedModelNode.value = null;
|
||||
selectedNodeTaskElement.value = null;
|
||||
|
||||
@@ -313,13 +436,19 @@ export default defineComponent({
|
||||
handleGraphEvent('node:dblclick', (args: any) => {
|
||||
const node = args.node as Node<NodeProperties>;
|
||||
const element = node.getData() as GraphTaskElement;
|
||||
console.error('element',element)
|
||||
window.location.href = `/app/decision/designer?scenario=${currentScenario.value?.id}&platform=${element?.platformId?? ''}`
|
||||
|
||||
window.location.href = `/app/decision/designer?scenarioId=${currentScenario.value?.id}&behaviortreeId=${element?.behaviortreeId ?? ''}`
|
||||
|
||||
// destroy()
|
||||
// window.location.href = '/app/decision/designer'
|
||||
|
||||
// 通过router跳转在初始化节点上存在渲染时机的问题,导致节点未渲染
|
||||
// router.push({
|
||||
// path: '/app/decision/designer'
|
||||
// path: '/app/decision/designer',
|
||||
// query: {
|
||||
// scenarioId: currentScenario.value?.id?.toString() ?? '',
|
||||
// behaviortreeId: element?.behaviortreeId?.toString() ?? '',
|
||||
// },
|
||||
// })
|
||||
});
|
||||
|
||||
@@ -380,10 +509,12 @@ export default defineComponent({
|
||||
|
||||
const handleSave = () => {
|
||||
const graphData: GraphContainer = resolveGraph(graph.value as Graph);
|
||||
console.log(graph.value);
|
||||
|
||||
|
||||
|
||||
const relations = resolveConnectionRelation(graph.value as Graph);
|
||||
console.error('relations',relations)
|
||||
|
||||
console.info('handleSave', graphData);
|
||||
if (!currentScenario.value) {
|
||||
message.error('当前决策树不存在');
|
||||
@@ -415,6 +546,193 @@ export default defineComponent({
|
||||
});
|
||||
};
|
||||
|
||||
// 随机生成节点流图
|
||||
const handleGenerateRandom = () => {
|
||||
if (!graph.value) {
|
||||
message.error('画布未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成随机数据
|
||||
const { records, graph: randomGraph } = generateRandomCommunicationData(30);
|
||||
|
||||
console.log('生成的随机数据:', records);
|
||||
console.log('转换后的图数据:', randomGraph);
|
||||
|
||||
// 清空现有内容
|
||||
graph.value.clearCells();
|
||||
|
||||
// 设置当前场景
|
||||
if (!currentScenario.value) {
|
||||
currentScenario.value = {
|
||||
id: 0,
|
||||
name: `随机场景_${Date.now()}`,
|
||||
description: '自动生成的测试场景',
|
||||
communicationGraph: null,
|
||||
relations: [],
|
||||
graph: randomGraph,
|
||||
};
|
||||
} else {
|
||||
currentScenario.value.graph = randomGraph;
|
||||
currentScenario.value.communicationGraph = JSON.stringify(randomGraph);
|
||||
}
|
||||
|
||||
// 渲染节点
|
||||
setTimeout(() => {
|
||||
if (randomGraph.nodes) {
|
||||
randomGraph.nodes.forEach(ele => {
|
||||
const node = createGraphScenarioElement(ele as GraphTaskElement);
|
||||
graph.value?.addNode(node as Node);
|
||||
});
|
||||
}
|
||||
|
||||
// 延迟添加边,确保节点已渲染
|
||||
setTimeout(() => {
|
||||
if (randomGraph.edges) {
|
||||
randomGraph.edges.forEach(edgeData => {
|
||||
graph.value?.addEdge({
|
||||
...edgeData,
|
||||
...createLineOptions(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 自动适应视图
|
||||
fitToScreen();
|
||||
|
||||
message.success(`已生成 ${randomGraph.nodes.length} 个节点和 ${randomGraph.edges.length} 条连接线`);
|
||||
}, 100);
|
||||
}, 50);
|
||||
|
||||
} catch (error) {
|
||||
console.error('随机生成时出错:', error);
|
||||
message.error('生成失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 从后端加载平台数据并转换为通信关系图(当前使用模拟数据)
|
||||
const handleLoadFromBackend = async () => {
|
||||
if (!graph.value || !currentScenario.value) {
|
||||
message.error('请先选择场景');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
message.loading({ content: '正在加载通信关系数据...', key: 'loading' });
|
||||
|
||||
// 调用真实API获取通信关系
|
||||
console.log(`正在从后端加载场景 ${currentScenario.value.id} 的通信关系...`);
|
||||
const response = await findRelations(currentScenario.value.id);
|
||||
|
||||
console.log('API完整响应:', response);
|
||||
console.log('response.data类型:', typeof response.data, Array.isArray(response.data) ? 'Array' : 'Object');
|
||||
|
||||
// API返回的是 CommunicationRelationRecord[],与 CommunicationRecord 结构兼容
|
||||
// 处理可能的多种返回格式
|
||||
let relations: any[] = [];
|
||||
if (Array.isArray(response.data)) {
|
||||
relations = response.data;
|
||||
} else if (response.data && Array.isArray((response.data as any).data)) {
|
||||
relations = (response.data as any).data;
|
||||
} else if (response.data && Array.isArray((response.data as any).rows)) {
|
||||
relations = (response.data as any).rows;
|
||||
} else if (response.data && Array.isArray((response.data as any).list)) {
|
||||
relations = (response.data as any).list;
|
||||
}
|
||||
|
||||
console.log('解析后的通信关系数量:', relations.length);
|
||||
if (relations.length > 0) {
|
||||
console.log('第一条记录:', JSON.stringify(relations[0], null, 2));
|
||||
}
|
||||
|
||||
// 后端返回的是驼峰命名,需要转换为下划线命名以匹配前端类型
|
||||
const normalizedRelations = relations.map((item: any) => ({
|
||||
id: item.id,
|
||||
command_platform: item.commandPlatform || item.command_platform,
|
||||
subordinate_platform: item.subordinatePlatform || item.subordinate_platform,
|
||||
command_comm: item.commandComm || item.command_comm,
|
||||
subordinate_comm: item.subordinateComm || item.subordinate_comm,
|
||||
scenary_id: item.scenaryId || item.scenary_id,
|
||||
}));
|
||||
|
||||
console.log('标准化后的第一条记录:', normalizedRelations[0]);
|
||||
|
||||
if (normalizedRelations.length === 0) {
|
||||
console.warn('API未返回任何通信关系数据,使用模拟数据作为fallback');
|
||||
// Fallback到模拟数据(保留以便测试)
|
||||
relations.push(
|
||||
{ id: 6, command_platform: 'chief', subordinate_platform: 'task1_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 7, command_platform: 'chief', subordinate_platform: 'task2_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 8, command_platform: 'chief', subordinate_platform: 'task3_commander', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 9, command_platform: 'task1_commander', subordinate_platform: 'platform1', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 10, command_platform: 'task1_commander', subordinate_platform: 'platform3', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 11, command_platform: 'task1_commander', subordinate_platform: 'platform4', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 12, command_platform: 'task1_commander', subordinate_platform: 'platform5', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 13, command_platform: 'task1_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 14, command_platform: 'task2_commander', subordinate_platform: 'platform3', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 15, command_platform: 'task2_commander', subordinate_platform: 'platform5', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 16, command_platform: 'task2_commander', subordinate_platform: 'platform4', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 17, command_platform: 'task3_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 18, command_platform: 'task3_commander', subordinate_platform: 'platform6', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 19, command_platform: 'task3_commander', subordinate_platform: 'platform7', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
{ id: 20, command_platform: 'task3_commander', subordinate_platform: 'platform8', command_comm: 'radio', subordinate_comm: 'radio', scenary_id: currentScenario.value.id },
|
||||
);
|
||||
}
|
||||
|
||||
console.log('最终使用的通信记录:', normalizedRelations);
|
||||
|
||||
// 使用数据进行转换
|
||||
const convertedGraph = convertRecordsToGraphContainer(normalizedRelations);
|
||||
|
||||
console.log('转换后的图数据:', convertedGraph);
|
||||
|
||||
// 清空现有内容
|
||||
graph.value.clearCells();
|
||||
|
||||
// 更新当前场景
|
||||
currentScenario.value.graph = convertedGraph;
|
||||
currentScenario.value.communicationGraph = JSON.stringify(convertedGraph);
|
||||
|
||||
// 渲染节点
|
||||
setTimeout(() => {
|
||||
if (convertedGraph.nodes) {
|
||||
convertedGraph.nodes.forEach(ele => {
|
||||
const node = createGraphScenarioElement(ele as GraphTaskElement);
|
||||
graph.value?.addNode(node as Node);
|
||||
});
|
||||
}
|
||||
|
||||
// 延迟添加边,确保节点已渲染
|
||||
setTimeout(() => {
|
||||
if (convertedGraph.edges) {
|
||||
convertedGraph.edges.forEach(edgeData => {
|
||||
graph.value?.addEdge({
|
||||
...edgeData,
|
||||
...createLineOptions(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 自动适应视图
|
||||
fitToScreen();
|
||||
|
||||
message.success({
|
||||
content: `已从后端加载 ${convertedGraph.nodes.length} 个平台和 ${convertedGraph.edges.length} 条连接关系`,
|
||||
key: 'loading'
|
||||
});
|
||||
}, 100);
|
||||
}, 50);
|
||||
|
||||
} catch (error) {
|
||||
console.error('从后端加载时出错:', error);
|
||||
message.error({
|
||||
content: error instanceof Error ? error.message : '加载失败,请重试',
|
||||
key: 'loading'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
init();
|
||||
@@ -428,7 +746,6 @@ export default defineComponent({
|
||||
handleCreate,
|
||||
currentScenarioEditing,
|
||||
currentScenario,
|
||||
currentGraph,
|
||||
selectedNodeTaskElement,
|
||||
selectedModelNode,
|
||||
graph,
|
||||
@@ -444,6 +761,8 @@ export default defineComponent({
|
||||
handleDrop,
|
||||
isDraggingOver,
|
||||
handleSave,
|
||||
handleGenerateRandom,
|
||||
handleLoadFromBackend,
|
||||
handleUpdateElement,
|
||||
handleSelect,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
|
||||
<div>
|
||||
<a-dropdown
|
||||
:trigger="['contextmenu']"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
@openChange="handleVisibleChange"
|
||||
>
|
||||
<a-card
|
||||
:class="[
|
||||
'ks-scenario-node',
|
||||
@@ -55,6 +60,27 @@
|
||||
|
||||
<template #overlay>
|
||||
<a-menu @click="handleMenuClick">
|
||||
<a-sub-menu key="mount">
|
||||
<template #icon>
|
||||
<LinkOutlined />
|
||||
</template>
|
||||
<template #title>挂载</template>
|
||||
<a-menu-item
|
||||
v-for="tree in availableTrees"
|
||||
:key="`tree-${tree.id}`"
|
||||
:disabled="isTreeMounted(tree.id)"
|
||||
@click="() => handleMountTree(tree)"
|
||||
>
|
||||
<template #icon>
|
||||
<CheckOutlined v-if="isTreeMounted(tree.id)" />
|
||||
</template>
|
||||
{{ tree.name }}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="availableTrees.length === 0" disabled>
|
||||
暂无可用行为树
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="delete">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
@@ -64,21 +90,27 @@
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { elementProps, type ModelElement } from '../graph';
|
||||
|
||||
import { DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
||||
import { DeleteOutlined, LinkOutlined, CheckOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
||||
import type { Graph } from '@antv/x6';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { substring } from '@/utils/strings';
|
||||
import { updateBehaviorTree, updateBehaviorTreeIdOfPlatform } from './api';
|
||||
import type { BehaviorTree } from '../designer/tree';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModelElement',
|
||||
components: {
|
||||
SettingOutlined,
|
||||
DeleteOutlined,
|
||||
LinkOutlined,
|
||||
CheckOutlined,
|
||||
},
|
||||
props: elementProps,
|
||||
setup(_props) {
|
||||
@@ -88,6 +120,17 @@ export default defineComponent({
|
||||
const updateKey = ref(0);
|
||||
const isMenuVisible = ref(false);
|
||||
|
||||
// 挂载行为树相关状态
|
||||
const availableTrees = ref<BehaviorTree[]>([]);
|
||||
|
||||
// 获取 popup 容器
|
||||
const getPopupContainer = () => {
|
||||
if (typeof document !== 'undefined') {
|
||||
return document.body;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// 获取画布实例
|
||||
const getGraph = (): Graph | null => {
|
||||
return _props.graph as Graph || null;
|
||||
@@ -103,8 +146,35 @@ export default defineComponent({
|
||||
updateKey.value++;
|
||||
};
|
||||
|
||||
// 获取行为树名称
|
||||
const getBehaviorTreeName = (treeId: number | undefined | null): string => {
|
||||
if (!treeId) return '';
|
||||
const tree = availableTrees.value.find(t => t.id === treeId);
|
||||
return tree?.name || `行为树${treeId}`;
|
||||
};
|
||||
|
||||
// 判断行为树是否已挂载到当前节点
|
||||
const isTreeMounted = (treeId: number): boolean => {
|
||||
if (!element.value) return false;
|
||||
const currentTreeId = (element.value as any).behaviortreeId as number | undefined;
|
||||
return currentTreeId === treeId;
|
||||
};
|
||||
|
||||
// 处理挂载行为树 - 当右键菜单打开时从graph中读取已缓存的行为树列表
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
isMenuVisible.value = visible;
|
||||
|
||||
if (!visible || !element.value) return;
|
||||
|
||||
// 从graph对象中获取已缓存的行为树列表
|
||||
const graph = _props.graph as any;
|
||||
if (graph?.behaviorTrees) {
|
||||
availableTrees.value = graph.behaviorTrees;
|
||||
console.log('从缓存中读取行为树列表:', availableTrees.value.length, '个');
|
||||
} else {
|
||||
availableTrees.value = [];
|
||||
console.warn('未找到缓存的行为树列表');
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuClick = ({ key }: { key: string }) => {
|
||||
@@ -113,6 +183,35 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
// 处理挂载具体的行为树
|
||||
const handleMountTree = async (tree: BehaviorTree) => {
|
||||
if (!element.value) return;
|
||||
|
||||
try {
|
||||
// 更新节点的behaviortreeId属性
|
||||
const updatedElement = { ...(element.value as any), behaviortreeId: tree.id };
|
||||
|
||||
// 调用后端API:同时更新平台表的 behaviortreeId 和行为树表的 platformId
|
||||
const platformIdValue = (element.value as any).platformId as number;
|
||||
const [platformRes, treeRes] = await Promise.all([
|
||||
updateBehaviorTreeIdOfPlatform({ id: platformIdValue, behaviortreeId: tree.id }),
|
||||
updateBehaviorTree({ ...tree, platformId: platformIdValue }),
|
||||
]);
|
||||
if (platformRes.code === 200 && treeRes.code === 200) {
|
||||
// 更新本地节点数据
|
||||
if (_props.node) {
|
||||
_props.node.setData(updatedElement);
|
||||
}
|
||||
message.success(`已成功挂载行为树: ${tree.name}`);
|
||||
} else {
|
||||
message.error(platformRes.msg || treeRes.msg || '挂载失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('挂载行为树失败:', error);
|
||||
message.error('挂载行为树失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!_props.node) return;
|
||||
|
||||
@@ -134,10 +233,37 @@ export default defineComponent({
|
||||
|
||||
onMounted(() => {
|
||||
_props.node?.on('change:data', handleDataChange);
|
||||
|
||||
// 监听画布各种事件,操作时立即关闭菜单
|
||||
const graph = getGraph();
|
||||
if (graph) {
|
||||
const closeMenuHandler = () => {
|
||||
if (isMenuVisible.value) {
|
||||
isMenuVisible.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听多种可能导致菜单位置变化的事件
|
||||
graph.on('pan', closeMenuHandler);
|
||||
graph.on('translate', closeMenuHandler);
|
||||
graph.on('scale', closeMenuHandler);
|
||||
graph.on('zoom', closeMenuHandler);
|
||||
graph.on('resize', closeMenuHandler);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
_props.node?.off('change:data', handleDataChange);
|
||||
|
||||
// 清理事件监听
|
||||
const graph = getGraph();
|
||||
if (graph) {
|
||||
graph.off('pan');
|
||||
graph.off('translate');
|
||||
graph.off('scale');
|
||||
graph.off('zoom');
|
||||
graph.off('resize');
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -145,6 +271,11 @@ export default defineComponent({
|
||||
substring,
|
||||
handleMenuClick,
|
||||
handleVisibleChange,
|
||||
availableTrees,
|
||||
getBehaviorTreeName,
|
||||
isTreeMounted,
|
||||
handleMountTree,
|
||||
getPopupContainer,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export function resolveConnectionRelation(graph: Graph): PlatformRelation[] {
|
||||
// 过滤无效/临时边
|
||||
const validEdges = edges.filter(edge => {
|
||||
// 过滤临时边(X6 拖拽连线时生成的未完成边)
|
||||
const isTempEdge = edge?.attr('line/stroke') === 'transparent' || edge.id.includes('temp');
|
||||
const isTempEdge = edge?.attr('line/stroke') === 'transparent' || String(edge.id).includes('temp');
|
||||
if (isTempEdge) {
|
||||
tempEdgeIds.add(edge.id);
|
||||
return false;
|
||||
|
||||
@@ -48,3 +48,22 @@ export interface ScenarioDetailsResponse extends ApiDataResponse<Scenario> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通信关系记录(对应数据库表结构)
|
||||
*/
|
||||
export interface CommunicationRelationRecord {
|
||||
id: number;
|
||||
command_platform: string; // 指挥平台名称
|
||||
subordinate_platform: string; // 下属平台名称
|
||||
command_comm?: string; // 指挥端通信方式
|
||||
subordinate_comm?: string; // 下属端通信方式
|
||||
scenary_id?: number; // 场景ID
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景所有通信关系的响应
|
||||
*/
|
||||
export interface CommunicationRelationsResponse extends ApiDataResponse<CommunicationRelationRecord[]> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,53 @@
|
||||
import { HttpRequestClient } from '@/utils/request';
|
||||
import type { NodeTemplatesResponse } from './template';
|
||||
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, BehaviorTreeRequest } from './tree';
|
||||
import type { BasicResponse } from '@/types';
|
||||
import type { BasicResponse, PageableResponse } from '@/types';
|
||||
import type { PlatformListableResponse } from '../types';
|
||||
|
||||
|
||||
|
||||
export interface TreeTemplateRow {
|
||||
id: number;
|
||||
type: string;
|
||||
name: string;
|
||||
multiable?: boolean;
|
||||
}
|
||||
|
||||
export interface TreeNodeInstanceRow {
|
||||
id: number;
|
||||
treeId: number;
|
||||
templateId: number;
|
||||
instanceName: string;
|
||||
isRoot: number;
|
||||
}
|
||||
|
||||
export interface TreeConnectionRow {
|
||||
id: number;
|
||||
treeId: number;
|
||||
parentNodeId: number;
|
||||
childNodeId: number;
|
||||
orderIndex: number;
|
||||
}
|
||||
|
||||
export interface TreeTemplateParameterDefRow {
|
||||
id: number;
|
||||
templateId: number;
|
||||
paramKey: string;
|
||||
dataType: string;
|
||||
defaultValue: string;
|
||||
description: string;
|
||||
templateType: string;
|
||||
}
|
||||
|
||||
export interface TreeNodeParameterRow {
|
||||
id: number;
|
||||
treeId: number;
|
||||
nodeInstanceId: number;
|
||||
paramDefId: number;
|
||||
value: string;
|
||||
groupIndex: number;
|
||||
}
|
||||
|
||||
|
||||
const req = HttpRequestClient.create<BasicResponse>({
|
||||
baseURL: '/api',
|
||||
@@ -19,6 +65,20 @@ const req = HttpRequestClient.create<BasicResponse>({
|
||||
export const findNodeTemplates = (): Promise<NodeTemplatesResponse> => {
|
||||
return req.get('/system/nodetemplate/all');
|
||||
};
|
||||
export const findTemplateParameterDefs = (query={pageSize: 1000, pageNum: 1}): Promise<PageableResponse<TreeTemplateParameterDefRow>> => {
|
||||
return req.get('/system/templateparameterdef/list', query);
|
||||
}
|
||||
export const findNodeConnections = (query: {treeId: number}): Promise<PageableResponse<TreeConnectionRow>> => {
|
||||
return req.get('/system/nodeconnection/list', {pageSize: 1000, pageNum: 1, ...query});
|
||||
};
|
||||
export const findNodeParameters = (query: {treeId: number}): Promise<PageableResponse<TreeNodeParameterRow>> => {
|
||||
return req.get('/system/nodeparameter/list', {pageSize: 1000, pageNum: 1, ...query});
|
||||
}
|
||||
export const findTreeNodeInstances = (query: {treeId: number}): Promise<PageableResponse<TreeNodeInstanceRow>> => {
|
||||
return req.get('/system/treenodeinstance/list', {pageSize: 1000, pageNum: 1, ...query});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const findTreesByQuery = (query: Partial<BehaviorTreeRequest> = {}): Promise<BehaviorTreePageResponse> => {
|
||||
return req.get<BehaviorTreePageResponse>('/system/behaviortree/list', query);
|
||||
@@ -28,8 +88,8 @@ export const findOneTreeByPlatformId = (platformId: number): Promise<BehaviorTre
|
||||
return req.get(`/system/behaviortree/platform/${platformId}`);
|
||||
};
|
||||
|
||||
export const findSubPlatforms = (treeId: number): Promise<BehaviorTreeDetailsResponse> => {
|
||||
return req.get(`/system/behaviortree/underling/${treeId}`);
|
||||
export const findSubPlatforms = (platformId: number): Promise<PlatformListableResponse> => {
|
||||
return req.get(`/system/behaviortree/underling/${platformId}`);
|
||||
};
|
||||
|
||||
export const findOneTreeById = (id: number): Promise<BehaviorTreeDetailsResponse> => {
|
||||
|
||||
@@ -5,8 +5,15 @@
|
||||
<a-layout class="ks-layout-body">
|
||||
<div class="ks-model-builder-body">
|
||||
<div class="ks-model-builder-left">
|
||||
<ScenariosCard
|
||||
ref="scenariosCardRef"
|
||||
:scenarioId="currentScenarioId"
|
||||
@select-scenario="handleSelectScenario"
|
||||
/>
|
||||
<TressCard
|
||||
ref="treesCardRef"
|
||||
:scenarioId="currentScenarioId"
|
||||
:treeId="currentTreeId"
|
||||
@create-tree="handleCreateTree"
|
||||
@select-tree="handleSelectTree"
|
||||
/>
|
||||
@@ -24,7 +31,7 @@
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
|
||||
<a-button v-if="currentScenarioId" class="ks-model-builder-save" size="small" @click="handleGoback">
|
||||
<a-button v-if="fromScenarioPage" class="ks-model-builder-save" size="small" @click="handleGoback">
|
||||
<BackwardFilled />
|
||||
<span>返回</span>
|
||||
</a-button>
|
||||
@@ -71,6 +78,7 @@ import Properties from './properties.vue';
|
||||
import type { NodeDragTemplate } from './template';
|
||||
import type { BehaviorTree } from './tree';
|
||||
import { createGraphTaskElementFromTemplate } from './utils';
|
||||
import { resolveBehaviorTreeGraph } from './reverse-tree/tree-graph-resolver';
|
||||
|
||||
import { createGraphTaskElement, createLineOptions, type GraphContainer, type GraphTaskElement, hasElements, hasRootElementNode, resolveGraph, useGraphCanvas } from '../graph';
|
||||
import { registerNodeElement } from './register';
|
||||
@@ -78,7 +86,9 @@ import { findAllBasicPlatforms, findAllNodeCommands } from '../api';
|
||||
import type { NodeCommand, Platform } from '../types';
|
||||
import { createTree, findOneTreeById, findOneTreeByPlatformId, updateTree, findSubPlatforms } from './api';
|
||||
import TressCard from './trees-card.vue';
|
||||
import ScenariosCard from './scenarios-card.vue';
|
||||
import NodesCard from './nodes-card.vue';
|
||||
import { generateKey } from '@/utils/strings';
|
||||
|
||||
const TeleportContainer = defineComponent(getTeleport());
|
||||
|
||||
@@ -86,6 +96,7 @@ registerNodeElement();
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ScenariosCard,
|
||||
TressCard,
|
||||
NodesCard,
|
||||
Wrapper,
|
||||
@@ -107,16 +118,18 @@ export default defineComponent({
|
||||
const isDraggingOver = ref(false);
|
||||
const currentTreeEditing = ref<boolean>(false);
|
||||
const currentBehaviorTree = ref<BehaviorTree | null>(null);
|
||||
const currentGraph = ref<GraphContainer | null>(null);
|
||||
const selectedModelNode = ref<Node<NodeProperties> | null>(null);
|
||||
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
|
||||
const changed = ref<boolean>(false);
|
||||
const scenariosCardRef = ref<InstanceType<typeof ScenariosCard> | null>(null);
|
||||
const treesCardRef = ref<InstanceType<typeof TressCard> | null>(null);
|
||||
const platforms = ref<Platform[]>([]);
|
||||
const subPlatforms = ref<Platform[]>([]);
|
||||
const nodeCommands = ref<NodeCommand[]>([])
|
||||
const currentScenarioId = ref<number | null>(null);
|
||||
const currentScenarioId = ref<number | undefined>();
|
||||
const currentPlatformId = ref<number | null>(null);
|
||||
const currentTreeId = ref<number | null>(null);
|
||||
const fromScenarioPage = ref<boolean>(false);
|
||||
|
||||
const {
|
||||
handleGraphEvent,
|
||||
@@ -156,20 +169,14 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 加载下属平台
|
||||
const loadSubPlatforms = (treeId: number) => {
|
||||
console.log(treeId);
|
||||
|
||||
if (!treeId || treeId <= 0) {
|
||||
const loadSubPlatforms = (platformId: number | null) => {
|
||||
if (!platformId || platformId <= 0) {
|
||||
subPlatforms.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
findSubPlatforms(treeId).then(r => {
|
||||
if (r.data && Array.isArray(r.data)) {
|
||||
subPlatforms.value = r.data as Platform[];
|
||||
} else {
|
||||
subPlatforms.value = [];
|
||||
}
|
||||
findSubPlatforms(platformId).then(r => {
|
||||
subPlatforms.value = r.data ?? [];
|
||||
}).catch(err => {
|
||||
console.error('加载下属平台失败:', err);
|
||||
subPlatforms.value = [];
|
||||
@@ -265,42 +272,24 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectTree = (tree: BehaviorTree) => {
|
||||
destroyGraph();
|
||||
currentPlatformId.value = null;
|
||||
currentScenarioId.value = null;
|
||||
|
||||
console.info('handleSelectTree', tree);
|
||||
findOneTreeById(tree.id).then(r => {
|
||||
if (r.data) {
|
||||
let nodeGraph: GraphContainer | null = null;
|
||||
try {
|
||||
nodeGraph = JSON.parse(r.data?.xmlContent as unknown as string) as unknown as GraphContainer;
|
||||
} catch (e: any) {
|
||||
console.error('parse error,cause:', e);
|
||||
}
|
||||
if (!nodeGraph) {
|
||||
nodeGraph = {
|
||||
const initGraphConfig = (_graph?: GraphContainer) => {
|
||||
const graph: GraphContainer = _graph ? _graph : {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
|
||||
currentBehaviorTree.value = {
|
||||
...r.data,
|
||||
graph: nodeGraph,
|
||||
id: 0,
|
||||
name: '行为树',
|
||||
description: null,
|
||||
englishName: generateKey('scenario'),
|
||||
xmlContent: null,
|
||||
createdAt: null,
|
||||
platformId: currentPlatformId.value,
|
||||
scenarioId: currentScenarioId.value,
|
||||
graph: { ...graph },
|
||||
updatedAt: null,
|
||||
};
|
||||
currentTreeEditing.value = true;
|
||||
|
||||
// 加载下属平台
|
||||
loadSubPlatforms(r.data.id);
|
||||
|
||||
nextTick(() => {
|
||||
initGraph();
|
||||
});
|
||||
} else {
|
||||
message.error(r.msg ?? '行为树不存在.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createElements = () => {
|
||||
@@ -311,72 +300,107 @@ export default defineComponent({
|
||||
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); // 延迟一会儿,免得连线错位
|
||||
});
|
||||
fitToScreen();
|
||||
|
||||
console.info('create elements: ', currentBehaviorTree.value?.graph);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const resolveQuery = ()=> {
|
||||
let scenarioId = Number(currentRoute.query.scenario);
|
||||
if (!isNaN(scenarioId)) {
|
||||
currentScenarioId.value = scenarioId;
|
||||
} else {
|
||||
currentScenarioId.value = null;
|
||||
}
|
||||
let platformId = Number(currentRoute.query.platform);
|
||||
if (!isNaN(platformId)) {
|
||||
currentPlatformId.value = platformId;
|
||||
} else {
|
||||
currentPlatformId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateTree = () => {
|
||||
const applyBehaviorTree = (tree: BehaviorTree) => {
|
||||
destroyGraph();
|
||||
|
||||
currentBehaviorTree.value = {
|
||||
id: 0,
|
||||
name: '行为树',
|
||||
description: null,
|
||||
englishName: null,
|
||||
xmlContent: null,
|
||||
createdAt: null,
|
||||
platformId: currentPlatformId.value,
|
||||
graph: {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
},
|
||||
updatedAt: null,
|
||||
};
|
||||
currentGraph.value = {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
};
|
||||
currentPlatformId.value = tree.platformId ?? null;
|
||||
currentBehaviorTree.value = tree;
|
||||
currentTreeEditing.value = true;
|
||||
selectedModelNode.value = null;
|
||||
selectedNodeTaskElement.value = null;
|
||||
subPlatforms.value = []; // 重置下属平台
|
||||
loadSubPlatforms(currentPlatformId.value);
|
||||
|
||||
nextTick(() => {
|
||||
initGraph();
|
||||
});
|
||||
};
|
||||
|
||||
const loadTargetTree = (tree: BehaviorTree) => {
|
||||
resolveBehaviorTreeGraph(tree.id, tree.xmlContent).then(nodeGraph => {
|
||||
applyBehaviorTree({
|
||||
...tree,
|
||||
graph: nodeGraph,
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('resolve tree graph error:', error);
|
||||
message.error('加载行为树图失败');
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateTree = () => {
|
||||
destroyGraph();
|
||||
|
||||
selectedModelNode.value = null;
|
||||
selectedNodeTaskElement.value = null;
|
||||
subPlatforms.value = []; // 重置下属平台
|
||||
|
||||
initGraphConfig();
|
||||
nextTick(() => {
|
||||
initGraph();
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectTree = (tree: BehaviorTree) => {
|
||||
console.info('handleSelectTree', tree);
|
||||
findOneTreeById(tree.id).then(r => {
|
||||
if (r.data) {
|
||||
currentTreeId.value = tree.id;
|
||||
loadTargetTree(r.data);
|
||||
} else {
|
||||
message.error(r.msg ?? '行为树不存在.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const resolveQuery = ()=> {
|
||||
console.log(currentRoute);
|
||||
if (!currentRoute.query.scenarioId) {
|
||||
return
|
||||
}
|
||||
|
||||
let scenarioId = Number(currentRoute.query.scenarioId);
|
||||
if (!isNaN(scenarioId)) {
|
||||
currentScenarioId.value = scenarioId;
|
||||
fromScenarioPage.value = true;
|
||||
} else {
|
||||
fromScenarioPage.value = false;
|
||||
}
|
||||
|
||||
let behaviortreeId = Number(currentRoute.query.behaviortreeId);
|
||||
if (!isNaN(behaviortreeId)) {
|
||||
currentTreeId.value = behaviortreeId;
|
||||
} else {
|
||||
currentTreeId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理选择场景
|
||||
const handleSelectScenario = (scenario: any) => {
|
||||
currentScenarioId.value = scenario.id;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 初始化X6画布
|
||||
const initGraph = () => {
|
||||
if (!canvas.value) {
|
||||
@@ -387,8 +411,15 @@ export default defineComponent({
|
||||
try {
|
||||
graph.value = createCanvas(canvas.value);
|
||||
console.log('画布初始化成功');
|
||||
|
||||
createElements();
|
||||
|
||||
graph.value?.on('edge:click', (args: any) => {
|
||||
const edge = args.edge;
|
||||
console.info('点击了连线:', args);
|
||||
// 这里可以添加选中连线的逻辑,比如显示属性面板等
|
||||
});
|
||||
|
||||
// 监听缩放变化
|
||||
handleGraphEvent('scale', ({ sx }: { sx: number }) => {
|
||||
currentZoom.value = sx;
|
||||
@@ -403,7 +434,6 @@ export default defineComponent({
|
||||
handleGraphEvent('node:click', (args: any) => {
|
||||
const node = args.node as Node<NodeProperties>;
|
||||
const newElement = node.getData() as GraphTaskElement;
|
||||
console.error('ddd', args);
|
||||
|
||||
selectedModelNode.value = node;
|
||||
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as GraphTaskElement;
|
||||
@@ -429,23 +459,28 @@ export default defineComponent({
|
||||
|
||||
const init = () => {
|
||||
console.info('init');
|
||||
nextTick(() => {
|
||||
initGraph();
|
||||
window.addEventListener('resize', handleResize);
|
||||
console.log('节点挂载完成');
|
||||
|
||||
resolveQuery();
|
||||
|
||||
if (currentPlatformId.value) {
|
||||
findOneTreeByPlatformId(currentPlatformId.value).then(r => {
|
||||
nextTick(() => {
|
||||
initGraphConfig();
|
||||
initGraph();
|
||||
console.log('节点挂载完成');
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
|
||||
|
||||
if (currentTreeId.value) {
|
||||
findOneTreeById(currentTreeId.value).then(r => {
|
||||
if (r.data) {
|
||||
handleSelectTree(r.data);
|
||||
currentPlatformId.value = r.data.platformId ?? null;
|
||||
loadTargetTree(r.data);
|
||||
} else {
|
||||
handleCreateTree();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
currentPlatformId.value = null;
|
||||
handleCreateTree();
|
||||
}
|
||||
});
|
||||
@@ -473,6 +508,7 @@ export default defineComponent({
|
||||
message.error('当前决策树不存在');
|
||||
return;
|
||||
}
|
||||
// return;
|
||||
const newTree: BehaviorTree = {
|
||||
...currentBehaviorTree.value,
|
||||
graph: graphData,
|
||||
@@ -508,19 +544,23 @@ export default defineComponent({
|
||||
loadDatasource();
|
||||
});
|
||||
|
||||
|
||||
// 清理
|
||||
onBeforeUnmount(() => destroy());
|
||||
|
||||
return {
|
||||
nodeCommands,
|
||||
currentScenarioId,
|
||||
currentPlatformId,
|
||||
currentTreeId,
|
||||
platforms,
|
||||
subPlatforms,
|
||||
scenariosCardRef,
|
||||
treesCardRef,
|
||||
handleCreateTree,
|
||||
handleSelectScenario,
|
||||
currentTreeEditing,
|
||||
currentBehaviorTree,
|
||||
currentGraph,
|
||||
selectedNodeTaskElement,
|
||||
selectedModelNode,
|
||||
graph,
|
||||
@@ -539,6 +579,7 @@ export default defineComponent({
|
||||
handleUpdateElement,
|
||||
handleSelectTree,
|
||||
handleGoback,
|
||||
fromScenarioPage,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
|
||||
|
||||
|
||||
<a-card
|
||||
:class="[
|
||||
'ks-designer-node',
|
||||
@@ -15,7 +18,7 @@
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<div class="port port-in" data-port="in-0" magnet="passive">
|
||||
<div class="port port-in" data-port="in-0" port="in-0" magnet="passive">
|
||||
<div class="triangle-left"></div>
|
||||
</div>
|
||||
<div class="w-full ks-designer-node-text">
|
||||
@@ -28,7 +31,7 @@
|
||||
</p>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="port port-out" data-port="out-0" magnet="active">
|
||||
<div class="port port-out" data-port="out-0" port="out-0" magnet="active">
|
||||
<div class="triangle-right" ></div>
|
||||
</div>
|
||||
</a-card>
|
||||
@@ -44,6 +47,7 @@
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import type { NodeDragTemplate, NodeTemplate } from './template';
|
||||
import { findNodeTemplates } from './api';
|
||||
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
|
||||
import { loadNodeTemplatesOnce } from './template-metadata-loader';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['drag-item-start', 'drag-item-end'],
|
||||
@@ -95,7 +95,7 @@ export default defineComponent({
|
||||
conditionTemplates.value = [];
|
||||
actionsTemplates.value = [];
|
||||
|
||||
findNodeTemplates().then(r => {
|
||||
loadNodeTemplatesOnce().then(r => {
|
||||
templateData.value = r.data;
|
||||
if (r.data) {
|
||||
r.data.forEach(tpl => {
|
||||
|
||||
@@ -108,12 +108,13 @@
|
||||
:placeholder="setting.description" size="small" style="width:100%;" />
|
||||
<a-select :placeholder="`请选择${setting.description}`"
|
||||
allow-clear
|
||||
v-else-if="setting.paramKey === 'platforms'" v-model:value="setting.defaultValue">
|
||||
v-else-if="setting.paramKey === 'platforms'"
|
||||
v-model:value="setting.defaultValue">
|
||||
<a-select-option v-for="pl in getAvailablePlatforms()" :value="pl.name">{{pl.description}}</a-select-option>
|
||||
</a-select>
|
||||
<a-select :placeholder="`请选择${setting.description}`"
|
||||
allow-clear
|
||||
v-else-if="setting.paramKey === 'command'" v-model:value="setting.defaultValue">
|
||||
v-else-if="isNodeCommandParameter(setting.paramKey as string | null | undefined)" v-model:value="setting.defaultValue">
|
||||
<a-select-option v-for="pl in nodeCommands" :value="pl.command">{{pl.chineseName}}</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-else v-model:value="setting.defaultValue"
|
||||
@@ -129,22 +130,32 @@
|
||||
</a-tabs>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item v-for="setting in currentElement.parameters" :label="setting.description">
|
||||
<template v-for="setting in currentElement.parameters">
|
||||
<div v-if="['lon','lat'].includes(setting.paramKey as string)">
|
||||
<div v-if="setting.paramKey==='lon'" class="ks-location-title">位置</div>
|
||||
<a-form-item class="ks-location-item" labelAlign="left" :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>
|
||||
</div>
|
||||
<a-form-item v-else :label="setting.description">
|
||||
<a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue"
|
||||
:placeholder="setting.description" size="small" style="width:100%;" />
|
||||
<a-select :placeholder="`请选择${setting.description}`"
|
||||
allow-clear
|
||||
v-else-if="setting.paramKey === 'platforms'" v-model:value="setting.defaultValue">
|
||||
<a-select-option v-for="pl in platforms" :value="pl.name">{{ pl.description }}</a-select-option>
|
||||
<a-select-option v-for="pl in getAvailablePlatforms()" :value="pl.name">{{ pl.description }}</a-select-option>
|
||||
</a-select>
|
||||
<a-select :placeholder="`请选择${setting.description}`"
|
||||
allow-clear
|
||||
v-else-if="setting.paramKey === 'command'" v-model:value="setting.defaultValue">
|
||||
v-else-if="isNodeCommandParameter(setting.paramKey as string | null | undefined)" v-model:value="setting.defaultValue">
|
||||
<a-select-option v-for="pl in nodeCommands" :value="pl.command">{{pl.chineseName}}</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-form>
|
||||
</template>
|
||||
<a-empty v-else>
|
||||
@@ -199,6 +210,10 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['update-element', 'update-tree'],
|
||||
setup(props, { emit }) {
|
||||
const isNodeCommandParameter = (paramKey: string | null | undefined): boolean => {
|
||||
return ['command', 'should_task'].includes(paramKey ?? '');
|
||||
};
|
||||
|
||||
const platforms = ref<Platform[]>(props.platforms ?? []);
|
||||
const subPlatforms = ref<Platform[]>(props.subPlatforms ?? []);
|
||||
const nodeCommands = ref<NodeCommand[]>(props.nodeCommands ?? []);
|
||||
@@ -231,7 +246,16 @@ export default defineComponent({
|
||||
|
||||
const addParameterTab = () => {
|
||||
let newParameters = dumpParameters();
|
||||
// 新增一个空的参数分组
|
||||
// 如果有下属平台,预填对应索引的平台名称
|
||||
const nextIndex = groupedParameters.value.length;
|
||||
const subPlatform = subPlatforms.value[nextIndex];
|
||||
if (subPlatform) {
|
||||
const platformParam = newParameters.find(p => p.paramKey === 'platforms');
|
||||
if (platformParam) {
|
||||
platformParam.defaultValue = subPlatform.name;
|
||||
}
|
||||
}
|
||||
// 新增一个参数分组
|
||||
groupedParameters.value.push(newParameters);
|
||||
// 自动切换到新增的分组
|
||||
groupedParametersActiveTab.value = groupedParameters.value.length - 1;
|
||||
@@ -292,33 +316,51 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
// 第二步:将 Map 转换为二维数组(按 groupIndex 升序排序)
|
||||
groupedParameters.value = Array.from(groupMap.entries())
|
||||
// 按分组索引从小到大排序(保证分组顺序正确)
|
||||
const rawGroups = Array.from(groupMap.entries())
|
||||
.sort((a, b) => a[0] - b[0])
|
||||
// 只保留分组后的参数数组,丢弃 key
|
||||
.map(item => item[1]);
|
||||
|
||||
// 第三步:展开逗号分隔的 platforms 值 —— 每个平台拆成独立分组
|
||||
const expandedGroups: Array<ElementParameter[]> = [];
|
||||
for (const group of rawGroups) {
|
||||
const platformParam = group.find(p => p.paramKey === 'platforms');
|
||||
if (platformParam) {
|
||||
const platformValues = String(platformParam.defaultValue ?? '')
|
||||
.split(',')
|
||||
.map(v => v.trim())
|
||||
.filter(Boolean);
|
||||
if (platformValues.length > 1) {
|
||||
// 每个平台值生成独立分组,其余参数保持一致
|
||||
for (const pv of platformValues) {
|
||||
const newGroup = JSON.parse(JSON.stringify(group)) as ElementParameter[];
|
||||
const pp = newGroup.find(p => p.paramKey === 'platforms');
|
||||
if (pp) pp.defaultValue = pv;
|
||||
expandedGroups.push(newGroup);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
expandedGroups.push(group);
|
||||
}
|
||||
|
||||
groupedParameters.value = expandedGroups;
|
||||
multiableParameters.value = multiable === true && groupedParameters.value.length > 0;
|
||||
};
|
||||
|
||||
// 获取平台Tab显示名称
|
||||
// 获取平台Tab显示名称(优先取该分组的 platforms 参数值,其次下属平台名称)
|
||||
const getPlatformTabName = (index: number): string => {
|
||||
if (!currentTree.value?.platformId) {
|
||||
const group = groupedParameters.value[index];
|
||||
if (group) {
|
||||
const platformParam = group.find(p => p.paramKey === 'platforms');
|
||||
if (platformParam?.defaultValue) {
|
||||
return String(platformParam.defaultValue);
|
||||
}
|
||||
}
|
||||
const sub = subPlatforms.value[index];
|
||||
if (sub) {
|
||||
return sub.name || sub.description || `平台 ${index + 1}`;
|
||||
}
|
||||
return `平台 ${index + 1}`;
|
||||
}
|
||||
|
||||
// 查找当前行为树绑定的平台
|
||||
const currentPlatform = platforms.value.find(p => p.id === currentTree.value?.platformId);
|
||||
if (!currentPlatform) {
|
||||
return `平台 ${index + 1}`;
|
||||
}
|
||||
|
||||
// 如果有多个分组,显示平台名称和索引
|
||||
if (groupedParameters.value.length > 1) {
|
||||
return `${currentPlatform.name || '未知平台'}-${index + 1}`;
|
||||
}
|
||||
|
||||
return currentPlatform.name || '平台';
|
||||
};
|
||||
|
||||
// 获取可用的平台列表(包括当前平台和其下属平台)
|
||||
@@ -440,6 +482,7 @@ export default defineComponent({
|
||||
groupedParameters,
|
||||
getPlatformTabName,
|
||||
getAvailablePlatforms,
|
||||
isNodeCommandParameter,
|
||||
actionSpaceColumns,
|
||||
activeTopTabsKey,
|
||||
activeBottomTabsKey,
|
||||
@@ -454,3 +497,45 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.ks-location-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
:deep(.ks-location-item) {
|
||||
.ant-form-item-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.ant-form-item-label {
|
||||
width: 48px;
|
||||
flex-shrink: 0;
|
||||
padding-bottom: 0;
|
||||
> label {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
.ant-form-item-control {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ks-parameter-setting-tabs {
|
||||
:deep(.ant-tabs-tab) {
|
||||
width: 120px;
|
||||
max-width: 120px;
|
||||
}
|
||||
:deep(.ant-tabs-tab-btn) {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { GraphContainer } from '../../graph';
|
||||
import { findNodeConnections, findNodeParameters, findTreeNodeInstances } from '../api';
|
||||
import type { TreeTemplateRow } from '../api';
|
||||
import { buildGraphFromReverseTreeRows } from './reverse-tree';
|
||||
import type { ReverseTreeRows } from './reverse-tree';
|
||||
import { loadNodeTemplatesOnce, loadTemplateParameterDefsOnce } from '../template-metadata-loader';
|
||||
|
||||
// 已反演过的树直接返回缓存图,无需重复请求
|
||||
const graphCache = new Map<number, GraphContainer>();
|
||||
|
||||
export const loadReverseTreeGraph = async (treeId: number): Promise<GraphContainer | null> => {
|
||||
const cached = graphCache.get(treeId);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 模板类接口只请求一次,树级接口每次切树请求
|
||||
const [nodeTemplatesResponse, templateParameterDefs, instancesResponse, connectionsResponse] =
|
||||
await Promise.all([
|
||||
loadNodeTemplatesOnce(),
|
||||
loadTemplateParameterDefsOnce(),
|
||||
findTreeNodeInstances({ treeId }),
|
||||
findNodeConnections({ treeId }),
|
||||
]);
|
||||
const parametersResponse = await findNodeParameters({ treeId });
|
||||
|
||||
const rows: ReverseTreeRows = {
|
||||
templates: (nodeTemplatesResponse.data ?? []).map(t => ({
|
||||
id: t.id,
|
||||
type: t.type ?? 'action',
|
||||
name: t.name ?? '',
|
||||
multiable: t.multiable,
|
||||
})) as TreeTemplateRow[],
|
||||
templateParameterDefs: templateParameterDefs,
|
||||
nodeInstances: instancesResponse.rows ?? [],
|
||||
connections: connectionsResponse.rows ?? [],
|
||||
nodeParameters: parametersResponse.rows ?? [],
|
||||
};
|
||||
|
||||
const graph = buildGraphFromReverseTreeRows(treeId, rows);
|
||||
if (graph) {
|
||||
graphCache.set(treeId, graph);
|
||||
}
|
||||
return graph;
|
||||
};
|
||||
|
||||
export const invalidateReverseTreeCache = (treeId: number): void => {
|
||||
graphCache.delete(treeId);
|
||||
};
|
||||
267
modeler/src/views/decision/designer/reverse-tree/reverse-tree.ts
Normal file
267
modeler/src/views/decision/designer/reverse-tree/reverse-tree.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
import type { ElementParameter, GraphContainer, GraphEdgeElement, GraphTaskElement } from '../../graph';
|
||||
import type {
|
||||
TreeConnectionRow as ReverseTreeConnectionRow,
|
||||
TreeNodeInstanceRow as ReverseTreeNodeInstanceRow,
|
||||
TreeNodeParameterRow as ReverseTreeNodeParameterRow,
|
||||
TreeTemplateParameterDefRow as ReverseTreeTemplateParameterDefRow,
|
||||
TreeTemplateRow as ReverseTreeTemplateRow,
|
||||
} from '../api';
|
||||
|
||||
export interface ReverseTreeRows {
|
||||
templates: ReverseTreeTemplateRow[];
|
||||
nodeInstances: ReverseTreeNodeInstanceRow[];
|
||||
connections: ReverseTreeConnectionRow[];
|
||||
templateParameterDefs: ReverseTreeTemplateParameterDefRow[];
|
||||
nodeParameters: ReverseTreeNodeParameterRow[];
|
||||
}
|
||||
|
||||
const NODE_WIDTH = 250;
|
||||
const CONTROL_HEIGHT = 60;
|
||||
const ACTION_HEIGHT = 120;
|
||||
const ROOT_X = 80;
|
||||
const ROOT_Y = 50;
|
||||
const LEVEL_Y_SPACING = 120;
|
||||
const SIBLING_X_SPACING = 280;
|
||||
|
||||
const createNodeKey = (treeId: number, nodeId: number): string => {
|
||||
return `reverse-tree-${treeId}-node-${nodeId}`;
|
||||
};
|
||||
|
||||
const createEdgeKey = (treeId: number, edgeId: number): string => {
|
||||
return `reverse-tree-${treeId}-edge-${edgeId}`;
|
||||
};
|
||||
|
||||
const createBehaviorTreeEdgeEndpoints = (sourceCell: string, targetCell: string) => {
|
||||
return {
|
||||
source: {
|
||||
cell: sourceCell,
|
||||
port: 'out-0',
|
||||
},
|
||||
target: {
|
||||
cell: targetCell,
|
||||
port: 'in-0',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const resolveGroup = (type: string): 'control' | 'condition' | 'action' => {
|
||||
if (type === 'root' || type === 'parallel' || type === 'select' || type === 'sequence') {
|
||||
return 'control';
|
||||
}
|
||||
if (type === 'condition') {
|
||||
return 'condition';
|
||||
}
|
||||
return 'action';
|
||||
};
|
||||
|
||||
const resolveTemplateType = (type: string): string => {
|
||||
if (type === 'select') {
|
||||
return 'selector';
|
||||
}
|
||||
return type;
|
||||
};
|
||||
|
||||
const resolveNodeHeight = (group: 'control' | 'condition' | 'action'): number => {
|
||||
return group === 'action' ? ACTION_HEIGHT : CONTROL_HEIGHT;
|
||||
};
|
||||
|
||||
const findRootNodeId = (
|
||||
instances: ReverseTreeNodeInstanceRow[],
|
||||
connections: ReverseTreeConnectionRow[],
|
||||
): number | null => {
|
||||
const rootNode = instances.find(item => item.isRoot === 1);
|
||||
if (rootNode) {
|
||||
return rootNode.id;
|
||||
}
|
||||
|
||||
const childIds = new Set(connections.map(item => item.childNodeId));
|
||||
const topNode = instances.find(item => !childIds.has(item.id));
|
||||
return topNode?.id ?? null;
|
||||
};
|
||||
|
||||
export const buildGraphFromReverseTreeRows = (
|
||||
treeId: number,
|
||||
rows: ReverseTreeRows,
|
||||
): GraphContainer | null => {
|
||||
const templates = rows.templates;
|
||||
const templateParameterDefs = rows.templateParameterDefs;
|
||||
const nodeParameters = rows.nodeParameters;
|
||||
const instances = rows.nodeInstances.filter(item => item.treeId === treeId);
|
||||
const connections = rows.connections
|
||||
.filter(item => item.treeId === treeId)
|
||||
.sort((left, right) => left.orderIndex - right.orderIndex);
|
||||
const treeNodeParameters = nodeParameters.filter(item => item.treeId === treeId);
|
||||
|
||||
if (instances.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const templateMap = new Map(templates.map(item => [item.id, item]));
|
||||
const templateParameterDefsMap = new Map<number, ReverseTreeTemplateParameterDefRow[]>();
|
||||
const instanceMap = new Map(instances.map(item => [item.id, item]));
|
||||
const childrenMap = new Map<number, ReverseTreeConnectionRow[]>();
|
||||
const nodeParametersMap = new Map<number, ReverseTreeNodeParameterRow[]>();
|
||||
|
||||
templateParameterDefs.forEach(definition => {
|
||||
const currentDefinitions = templateParameterDefsMap.get(definition.templateId) ?? [];
|
||||
currentDefinitions.push(definition);
|
||||
currentDefinitions.sort((left, right) => left.id - right.id);
|
||||
templateParameterDefsMap.set(definition.templateId, currentDefinitions);
|
||||
});
|
||||
|
||||
treeNodeParameters.forEach(parameter => {
|
||||
const currentParameters = nodeParametersMap.get(parameter.nodeInstanceId) ?? [];
|
||||
currentParameters.push(parameter);
|
||||
currentParameters.sort((left, right) => {
|
||||
if (left.groupIndex !== right.groupIndex) {
|
||||
return left.groupIndex - right.groupIndex;
|
||||
}
|
||||
return left.id - right.id;
|
||||
});
|
||||
nodeParametersMap.set(parameter.nodeInstanceId, currentParameters);
|
||||
});
|
||||
|
||||
connections.forEach(connection => {
|
||||
const siblingConnections = childrenMap.get(connection.parentNodeId) ?? [];
|
||||
siblingConnections.push(connection);
|
||||
siblingConnections.sort((left, right) => left.orderIndex - right.orderIndex);
|
||||
childrenMap.set(connection.parentNodeId, siblingConnections);
|
||||
});
|
||||
|
||||
const rootNodeId = findRootNodeId(instances, connections);
|
||||
if (!rootNodeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const leafCountCache = new Map<number, number>();
|
||||
const countLeaves = (nodeId: number): number => {
|
||||
const cachedCount = leafCountCache.get(nodeId);
|
||||
if (cachedCount) {
|
||||
return cachedCount;
|
||||
}
|
||||
|
||||
const children = childrenMap.get(nodeId) ?? [];
|
||||
if (children.length === 0) {
|
||||
leafCountCache.set(nodeId, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const totalLeaves = children.reduce((total, connection) => {
|
||||
return total + countLeaves(connection.childNodeId);
|
||||
}, 0);
|
||||
leafCountCache.set(nodeId, totalLeaves);
|
||||
return totalLeaves;
|
||||
};
|
||||
|
||||
const nodes: GraphTaskElement[] = [];
|
||||
const edges: GraphEdgeElement[] = [];
|
||||
|
||||
const buildNodeParameters = (instance: ReverseTreeNodeInstanceRow): ElementParameter[] => {
|
||||
const parameterDefinitions = templateParameterDefsMap.get(instance.templateId) ?? [];
|
||||
if (parameterDefinitions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parameterRows = nodeParametersMap.get(instance.id) ?? [];
|
||||
const groupIndexes = parameterRows.length > 0
|
||||
? Array.from(new Set(parameterRows.map(item => item.groupIndex))).sort((left, right) => left - right)
|
||||
: [0];
|
||||
|
||||
return groupIndexes.flatMap(groupIndex => {
|
||||
return parameterDefinitions.map(definition => {
|
||||
const parameterRow = parameterRows.find(item => item.paramDefId === definition.id && item.groupIndex === groupIndex);
|
||||
return {
|
||||
id: definition.id,
|
||||
templateId: definition.templateId,
|
||||
paramKey: definition.paramKey,
|
||||
dataType: definition.dataType,
|
||||
defaultValue: parameterRow?.value ?? definition.defaultValue,
|
||||
description: definition.description,
|
||||
templateType: definition.templateType,
|
||||
groupIndex,
|
||||
} as ElementParameter;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const walk = (nodeId: number, startX: number, y: number): number => {
|
||||
const instance = instanceMap.get(nodeId);
|
||||
if (!instance) {
|
||||
return startX;
|
||||
}
|
||||
|
||||
const template = templateMap.get(instance.templateId);
|
||||
const templateType = template?.type ?? 'action';
|
||||
const group = resolveGroup(templateType);
|
||||
const parameters = buildNodeParameters(instance);
|
||||
const leafCount = countLeaves(nodeId);
|
||||
const totalWidth = leafCount * SIBLING_X_SPACING;
|
||||
const centerX = startX + totalWidth / 2 - SIBLING_X_SPACING / 2;
|
||||
const currentNode: GraphTaskElement = {
|
||||
id: instance.id,
|
||||
key: createNodeKey(treeId, instance.id),
|
||||
type: 'task',
|
||||
template: instance.templateId,
|
||||
templateType: resolveTemplateType(templateType),
|
||||
name: instance.instanceName || template?.name || `节点 ${instance.id}`,
|
||||
category: group,
|
||||
group,
|
||||
description: instance.instanceName || template?.name || `节点 ${instance.id}`,
|
||||
multiable: template?.multiable === true,
|
||||
order: 0,
|
||||
position: {
|
||||
x: Math.round(centerX),
|
||||
y: Math.round(y),
|
||||
},
|
||||
width: NODE_WIDTH,
|
||||
height: resolveNodeHeight(group),
|
||||
inputs: null,
|
||||
outputs: null,
|
||||
parameters,
|
||||
variables: [],
|
||||
};
|
||||
|
||||
nodes.push(currentNode);
|
||||
|
||||
const children = childrenMap.get(nodeId) ?? [];
|
||||
let childStartX = startX;
|
||||
children.forEach(connection => {
|
||||
const childLeafCount = countLeaves(connection.childNodeId);
|
||||
walk(connection.childNodeId, childStartX, y + LEVEL_Y_SPACING + currentNode.height);
|
||||
|
||||
const childInstance = instanceMap.get(connection.childNodeId);
|
||||
const edge: GraphEdgeElement = {
|
||||
id: connection.id,
|
||||
key: createEdgeKey(treeId, connection.id),
|
||||
...createBehaviorTreeEdgeEndpoints(
|
||||
currentNode.key as string,
|
||||
createNodeKey(treeId, connection.childNodeId),
|
||||
),
|
||||
attrs: {},
|
||||
router: {},
|
||||
connector: null,
|
||||
};
|
||||
|
||||
edges.push(edge);
|
||||
|
||||
const currentEdges = ((currentNode as GraphTaskElement & { edges?: GraphEdgeElement[] }).edges ?? []).slice();
|
||||
currentEdges.push({
|
||||
...edge,
|
||||
sourceName: currentNode.name,
|
||||
targetName: childInstance?.instanceName ?? childInstance?.id ?? connection.childNodeId,
|
||||
});
|
||||
(currentNode as GraphTaskElement & { edges?: GraphEdgeElement[] }).edges = currentEdges;
|
||||
|
||||
childStartX += childLeafCount * SIBLING_X_SPACING;
|
||||
});
|
||||
|
||||
return centerX;
|
||||
};
|
||||
|
||||
walk(rootNodeId, ROOT_X, ROOT_Y);
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { GraphContainer } from '../../graph';
|
||||
import { loadReverseTreeGraph } from './reverse-tree-loader';
|
||||
|
||||
const EMPTY_GRAPH: GraphContainer = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
export const parseBehaviorTreeGraph = (xmlContent: string | null | undefined): GraphContainer | null => {
|
||||
if (!xmlContent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(xmlContent) as GraphContainer;
|
||||
} catch (error) {
|
||||
console.error('parse behavior tree graph error:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const hasBehaviorTreeNodes = (graph: GraphContainer | null | undefined): boolean => {
|
||||
return Boolean(graph?.nodes && graph.nodes.length > 0);
|
||||
};
|
||||
|
||||
export const loadBehaviorTreeGraphById = async (treeId: number): Promise<GraphContainer> => {
|
||||
return (await loadReverseTreeGraph(treeId)) ?? { ...EMPTY_GRAPH };
|
||||
};
|
||||
|
||||
export const resolveBehaviorTreeGraph = async (
|
||||
treeId: number,
|
||||
xmlContent: string | null | undefined,
|
||||
): Promise<GraphContainer> => {
|
||||
// void xmlContent;
|
||||
|
||||
// 如需恢复动态选择逻辑,可切回下面这段:
|
||||
const savedGraph = parseBehaviorTreeGraph(xmlContent);
|
||||
if (hasBehaviorTreeNodes(savedGraph)) {
|
||||
return savedGraph as GraphContainer;
|
||||
}
|
||||
|
||||
return loadBehaviorTreeGraphById(treeId);
|
||||
};
|
||||
138
modeler/src/views/decision/designer/scenarios-card.vue
Normal file
138
modeler/src/views/decision/designer/scenarios-card.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<a-collapse v-model:activeKey="activeKey" :accordion="false" class="ks-trees-collapse">
|
||||
<a-collapse-panel key="1" style="position: relative">
|
||||
<template #header>
|
||||
<a-flex>
|
||||
<span class="ks-model-builder-title-icon icon-model"></span>
|
||||
<span style="color: #82c4e9;font-size: 16px;">我的场景</span>
|
||||
</a-flex>
|
||||
</template>
|
||||
<div class="w-full" style="padding: 5px;">
|
||||
<a-flex>
|
||||
<a-input-search v-model:value="scenarioQuery.name" allowClear placeholder="场景名称" size="small" @search="loadScenarios()" />
|
||||
</a-flex>
|
||||
</div>
|
||||
<a-list :data-source="scenarios || []" size="small" style="min-height: 25vh">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item :class="{ 'ks-item-selected': scenarioId === item.id }" @click="()=> handleSelect(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>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<a-pagination
|
||||
v-model:current="scenarioQuery.pageNum"
|
||||
:page-size="scenarioQuery.pageSize"
|
||||
:total="totalScenarios"
|
||||
style="position: unset; margin: 5px 0;"
|
||||
simple size="small" @change="handleChange" />
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, watch, type PropType } from 'vue';
|
||||
import { CopyOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { Scenario, ScenarioRequest } from '../communication/types';
|
||||
import { findScenarioByQuery } from '../communication/api';
|
||||
import { substring } from '@/utils/strings';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
scenarioId: Number as PropType<number>,
|
||||
},
|
||||
emits: ['select-scenario'],
|
||||
components: {
|
||||
CopyOutlined,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
},
|
||||
setup(_props, { emit }) {
|
||||
const scenarios = ref<Scenario[]>([]);
|
||||
const scenarioQuery = ref<Partial<ScenarioRequest>>({
|
||||
name: null,
|
||||
pageNum: 1,
|
||||
pageSize: 8,
|
||||
});
|
||||
const activeKey = ref<number>(0);
|
||||
const totalScenarios = ref<number>(0);
|
||||
|
||||
const loadScenarios = (cb?: () => void) => {
|
||||
findScenarioByQuery(scenarioQuery.value).then(r => {
|
||||
scenarios.value = r.rows;
|
||||
totalScenarios.value = r.total ?? 0;
|
||||
if(cb) cb();
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (page: number, pageSize: number) => {
|
||||
scenarioQuery.value.pageNum = page;
|
||||
scenarioQuery.value.pageSize = pageSize;
|
||||
loadScenarios();
|
||||
};
|
||||
|
||||
const handleSelect = (record: Scenario) => {
|
||||
emit('select-scenario', record);
|
||||
};
|
||||
|
||||
const refresh = () => loadScenarios();
|
||||
|
||||
onMounted(() => {
|
||||
loadScenarios(() => {
|
||||
if (_props.scenarioId) {
|
||||
return;
|
||||
}
|
||||
// 默认选中第一个场景
|
||||
const selectedScenario = scenarios.value[0];
|
||||
if (selectedScenario) {
|
||||
emit('select-scenario', selectedScenario);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
refresh,
|
||||
totalScenarios,
|
||||
substring,
|
||||
activeKey,
|
||||
scenarios,
|
||||
scenarioQuery,
|
||||
loadScenarios,
|
||||
handleSelect,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.a-list-item {
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.a-list-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.icon-scenario::before {
|
||||
content: '\e6b8';
|
||||
font-family: 'iconfont';
|
||||
margin-right: 8px;
|
||||
color: #82c4e9;
|
||||
}
|
||||
|
||||
:deep(.ant-list-item.ks-item-selected) {
|
||||
background-color: rgba(130, 196, 233, 0.15);
|
||||
border-left: 3px solid #82c4e9;
|
||||
padding-left: 9px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { findNodeTemplates, findTemplateParameterDefs } from './api';
|
||||
import type { TreeTemplateParameterDefRow } from './api';
|
||||
import type { NodeTemplatesResponse } from './template';
|
||||
|
||||
// 模板列表:全局只请求一次
|
||||
let nodeTemplatesPromise: Promise<NodeTemplatesResponse> | null = null;
|
||||
|
||||
// 模板参数定义:全局只请求一次
|
||||
let templateParameterDefsPromise: Promise<TreeTemplateParameterDefRow[]> | null = null;
|
||||
|
||||
export const loadNodeTemplatesOnce = (): Promise<NodeTemplatesResponse> => {
|
||||
if (!nodeTemplatesPromise) {
|
||||
nodeTemplatesPromise = findNodeTemplates().catch(err => {
|
||||
// 请求失败时清除缓存,下次可以重试
|
||||
nodeTemplatesPromise = null;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
return nodeTemplatesPromise;
|
||||
};
|
||||
|
||||
export const loadTemplateParameterDefsOnce = (): Promise<TreeTemplateParameterDefRow[]> => {
|
||||
if (!templateParameterDefsPromise) {
|
||||
templateParameterDefsPromise = findTemplateParameterDefs()
|
||||
.then(r => r.rows ?? [])
|
||||
.catch(err => {
|
||||
templateParameterDefsPromise = null;
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
return templateParameterDefsPromise;
|
||||
};
|
||||
@@ -20,11 +20,13 @@ export interface BehaviorTree {
|
||||
xmlContent: NullableString,
|
||||
graph: GraphContainer
|
||||
platformId: null | number
|
||||
scenarioId?: number,
|
||||
}
|
||||
|
||||
export interface BehaviorTreeRequest extends BehaviorTree {
|
||||
pageNum: number,
|
||||
pageSize: number,
|
||||
scenarioId?: number,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<a-list :data-source="behaviorTrees || []" size="small" style="min-height: 25vh">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item @click="()=> handleSelect(item)">
|
||||
<a-list-item :class="{ 'ks-item-selected': treeId === item.id }" @click="()=> handleSelect(item)">
|
||||
<a-flex>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>
|
||||
@@ -50,19 +50,25 @@
|
||||
v-model:current="behaviorTreeQuery.pageNum"
|
||||
:page-size="behaviorTreeQuery.pageSize"
|
||||
:total="totalTress"
|
||||
style="position: unset; margin: 5px 0;"
|
||||
simple size="small" @change="handleChange" />
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, onMounted, ref, watch } from 'vue';
|
||||
import { CheckOutlined, CopyOutlined, DeleteOutlined, EditFilled, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { BehaviorTree, BehaviorTreeRequest } from './tree';
|
||||
import { copyTree, deleteOneTreeById, findTreesByQuery } from './api';
|
||||
import { substring } from '@/utils/strings';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
scenarioId: Number as PropType<number | undefined>,
|
||||
treeId: Number as PropType<number | null>,
|
||||
},
|
||||
emits: ['select-tree', 'create-tree'],
|
||||
components: {
|
||||
CheckOutlined,
|
||||
@@ -71,20 +77,37 @@ export default defineComponent({
|
||||
DeleteOutlined,
|
||||
EditFilled,
|
||||
},
|
||||
setup(_props, { emit }) {
|
||||
setup(props, { emit }) {
|
||||
const behaviorTrees = ref<BehaviorTree[]>([]);
|
||||
const behaviorTreeQuery = ref<Partial<BehaviorTreeRequest>>({
|
||||
name: null,
|
||||
pageNum: 1,
|
||||
pageSize: 8,
|
||||
scenarioId: props.scenarioId,
|
||||
});
|
||||
const activeKey = ref<number>(1);
|
||||
const totalTress = ref<number>(0);
|
||||
|
||||
const loadTress = () => {
|
||||
watch(
|
||||
() => props.scenarioId,
|
||||
() => {
|
||||
if (!props.scenarioId) {
|
||||
behaviorTrees.value = [];
|
||||
totalTress.value = 0;
|
||||
return;
|
||||
}
|
||||
behaviorTreeQuery.value.pageNum = 1;
|
||||
behaviorTreeQuery.value.scenarioId = props.scenarioId;
|
||||
loadTress();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function loadTress(cb?: () => void) {
|
||||
findTreesByQuery(behaviorTreeQuery.value).then(r => {
|
||||
behaviorTrees.value = r.rows;
|
||||
totalTress.value = r.total ?? 0;
|
||||
if (cb) cb();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -131,10 +154,6 @@ export default defineComponent({
|
||||
|
||||
const refresh = () => loadTress();
|
||||
|
||||
onMounted(() => {
|
||||
loadTress();
|
||||
});
|
||||
|
||||
return {
|
||||
refresh,
|
||||
totalTress,
|
||||
@@ -155,3 +174,11 @@ export default defineComponent({
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-list-item.ks-item-selected) {
|
||||
background-color: rgba(130, 196, 233, 0.15);
|
||||
border-left: 3px solid #82c4e9;
|
||||
padding-left: 9px;
|
||||
}
|
||||
</style>
|
||||
|
||||
1106
modeler/src/views/decision/graph/data-converter.ts
Normal file
1106
modeler/src/views/decision/graph/data-converter.ts
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user