40 Commits

Author SHA1 Message Date
libertyspy
c2bfb43d41 UPDATE: VERSION-20260315 2026-03-15 20:28:06 +08:00
libertyspy
c5d81a4c52 UPDATE: VERSION-20260315 2026-03-15 20:24:50 +08:00
libertyspy
6d76cb8d5e UPDATE: VERSION-20260315 2026-03-15 20:21:07 +08:00
libertyspy
b97837ec6a UPDATE: VERSION-20260315 2026-03-15 20:20:56 +08:00
libertyspy
f2e81c6e5c UPDATE: VERSION-20260315 2026-03-15 19:32:20 +08:00
libertyspy
83a38c6db8 UPDATE: VERSION-20260315 2026-03-15 16:36:07 +08:00
libertyspy
22e71a24d3 UPDATE: VERSION-20260315 2026-03-15 16:15:23 +08:00
libertyspy
6019738aca UPDATE: VERSION-20260314 2026-03-15 16:05:41 +08:00
MHW
7847dbc89f 26-03-15-09:53 火力规则模块:获取通信组件的所有平台和组件 增加返回平台description字段 2026-03-15 09:54:20 +08:00
MHW
cc01c9ece8 Merge remote-tracking branch 'origin/master' 2026-03-15 09:33:09 +08:00
MHW
8f29e230d0 Merge branch 'refs/heads/develop' 2026-03-15 09:32:14 +08:00
MHW
711d7bf3da 26-03-15-09:31 场景模块:分页查询所有场景,保存场景 2026-03-15 09:31:45 +08:00
libertyspy
29e17773af UPDATE: VERSION-20260314 2026-03-14 22:35:40 +08:00
libertyspy
db97d8a026 UPDATE: VERSION-20260314 2026-03-14 22:35:31 +08:00
libertyspy
2e55254412 UPDATE: VERSION-20260314 2026-03-14 22:35:21 +08:00
libertyspy
1504c3fc1b UPDATE: VERSION-20260314 2026-03-14 21:37:18 +08:00
libertyspy
0cf4c9b47e UPDATE: VERSION-20260314 2026-03-14 21:37:07 +08:00
libertyspy
fa0c93044c Merge remote-tracking branch 'origin/master' 2026-03-14 20:55:23 +08:00
libertyspy
33a77428db UPDATE: VERSION-20260314 2026-03-14 20:55:15 +08:00
MHW
26a89a66d1 Merge branch 'refs/heads/develop' 2026-03-14 20:47:07 +08:00
MHW
f72105134f 26-03-14-20:46 通过平台id获取平台下所有组件 2026-03-14 20:46:49 +08:00
libertyspy
2198e108a4 UPDATE: VERSION-20260314 2026-03-14 18:08:20 +08:00
libertyspy
fe94cec559 Merge remote-tracking branch 'origin/master' 2026-03-14 17:57:05 +08:00
MHW
af3a97175a Merge branch 'refs/heads/develop' 2026-03-14 14:31:12 +08:00
MHW
c1c67e826b 26-03-14-14:30 规则CRUD 2026-03-14 14:30:52 +08:00
MHW
8a946c4c84 Merge branch 'refs/heads/develop' 2026-03-14 11:18:23 +08:00
MHW
d9a55d0c95 26-03-14-11:17:行为树-保存场景配置 2026-03-14 11:17:55 +08:00
libertyspy
7b578f5d63 UPDATE: VERSION-20260313 2026-03-13 17:05:08 +08:00
MHW
dde470c9da 26-03-13-14:36:火力规则简单实现-删除重复模块 2026-03-13 14:52:29 +08:00
MHW
91adb9517e Merge remote-tracking branch 'origin/master'
# Conflicts:
#	auto-solution-admin/src/main/java/com/solution/web/controller/rule/RuleController.java
#	auto-solution-common/src/main/java/com/solution/common/constant/PlatformAndModuleConstants.java
#	pom.xml
2026-03-13 14:41:41 +08:00
MHW
99c100f2ac 26-03-13-14:36:火力规则简单实现 2026-03-13 14:36:31 +08:00
libertyspy
a2f2cbb185 UPDATE: VERSION-20260313 2026-03-13 10:51:19 +08:00
libertyspy
d96941ea9b UPDATE: VERSION-20260313 2026-03-13 10:45:46 +08:00
libertyspy
5a7b57d603 UPDATE: VERSION-20260313 2026-03-13 10:42:39 +08:00
libertyspy
27f8810401 Initial commit 2026-03-13 10:41:43 +08:00
libertyspy
4980ba2da4 Initial commit 2026-03-13 10:40:44 +08:00
libertyspy
3a086e9405 Initial commit 2026-03-13 10:15:14 +08:00
f2f8892276 Merge branch 'develop' 2026-03-12 16:04:39 +08:00
152a7b59af Merge branch 'liangyun' into develop 2026-03-12 16:04:30 +08:00
6fb020355b 配置前端动作分类 2026-03-12 10:16:56 +08:00
107 changed files with 3362 additions and 676 deletions

View File

@@ -76,6 +76,11 @@
<artifactId>solution-rule</artifactId>
</dependency>
<dependency>
<groupId>com.solution</groupId>
<artifactId>solution-scene</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -114,4 +114,5 @@ public class BehaviortreeController extends BaseController
{
return toAjax(behaviortreeService.deleteBehaviortreeByIds(ids));
}
}

View File

@@ -0,0 +1,76 @@
package com.solution.web.controller.rule;
import com.solution.common.core.controller.BaseController;
import com.solution.common.core.domain.AjaxResult;
import com.solution.rule.domain.FireRuleExecuteDTO;
import com.solution.rule.service.FireRuleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Api("火力规则")
@RestController
@RequestMapping("/api/system/firerule")
public class FireRuleController extends BaseController {
@Autowired
private FireRuleService ruleService;
/**
* 开始执行规则匹配
* @param fireRuleExecuteDTO 敌方参数
* @return
*/
@PostMapping("/start")
@ApiOperation("开始执行规则匹配")
public AjaxResult execute(@RequestBody FireRuleExecuteDTO fireRuleExecuteDTO){
return success(ruleService.execute(fireRuleExecuteDTO));
}
/**
* 获取所有武器平台和组件
* @return
*/
@GetMapping("/weapon")
@ApiOperation("获取所有武器平台和组件")
public AjaxResult getPlatformComponentNames(){
return success(ruleService.getPlatformComponentNames());
}
/**
* 获取通信组件的所有平台和组件
* @param scenarioId
* @return
*/
@GetMapping("/comm")
@ApiOperation("获取通信组件的所有平台和组件")
public AjaxResult getCommPlatformComponentNames(Integer scenarioId){
return success(ruleService.getCommPlatformComponentNames(scenarioId));
}
/**
* 根据场景id获取所有平台及其组件
* @param scenarioId
* @return
*/
@GetMapping("/platforms/{scenarioId}")
public AjaxResult platforms(@PathVariable Integer scenarioId){
return success(ruleService.findPlatformComponents(scenarioId));
}
/**
* 根据平台id获取平台下所有组件
* @param platformId
* @return
*/
@GetMapping("/component/{platformId}")
@ApiOperation("根据平台id获取平台下所有组件")
public AjaxResult getComponents(@PathVariable Integer platformId){
return success(ruleService.getComponents(platformId));
}
}

View File

@@ -1,51 +1,65 @@
package com.solution.web.controller.rule;
import com.solution.common.annotation.Log;
import com.solution.common.core.controller.BaseController;
import com.solution.common.core.domain.AjaxResult;
import com.solution.rule.domain.dto.RequestDTO;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.WeaponModelVO;
import com.solution.rule.service.RuleService;
import com.solution.common.core.page.TableDataInfo;
import com.solution.common.enums.BusinessType;
import com.solution.rule.domain.Rule;
import com.solution.rule.service.IRuleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
@Api("火力规则")
@Api("红蓝对抗规则管理")
@RestController
@RequestMapping("/api/system/firerule")
@RequestMapping("/api/system/rule")
public class RuleController extends BaseController {
@Autowired
private RuleService ruleService;
private IRuleService ruleService;
/**
* 开始执行规则匹配
* @param sceneType 场景参数
* @param weaponModelDTO 敌方参数
* @return
*/
@PostMapping("/start")
@ApiOperation("开始执行规则匹配")
public AjaxResult execute(Integer sceneType, WeaponModelDTO weaponModelDTO){
return success(ruleService.execute(sceneType,weaponModelDTO));
@PreAuthorize("@ss.hasPermi('system:rule:list')")
@GetMapping("/list")
@ApiOperation("查询规则列表")
public TableDataInfo list(Rule rule) {
startPage();
List<Rule> list = ruleService.selectRuleList(rule);
return getDataTable(list);
}
/**
* 获取所有武器平台和组件
* @return
*/
@GetMapping("/weapon")
@ApiOperation("获取所有武器平台和组件")
public AjaxResult getPlatformComponentNames(){
return success(ruleService.getPlatformComponentNames());
@PreAuthorize("@ss.hasPermi('system:rule:query')")
@GetMapping("/{id}")
@ApiOperation("获取规则详情")
public AjaxResult getInfo(@PathVariable Integer id) {
return success(ruleService.selectRuleById(id));
}
}
@PreAuthorize("@ss.hasPermi('system:rule:add')")
@Log(title = "规则管理", businessType = BusinessType.INSERT)
@PostMapping
@ApiOperation("新增规则")
public AjaxResult add(@RequestBody Rule rule) {
return toAjax(ruleService.insertRule(rule));
}
@PreAuthorize("@ss.hasPermi('system:rule:edit')")
@Log(title = "规则管理", businessType = BusinessType.UPDATE)
@PutMapping
@ApiOperation("修改规则")
public AjaxResult edit(@RequestBody Rule rule) {
return toAjax(ruleService.updateRule(rule));
}
@PreAuthorize("@ss.hasPermi('system:rule:remove')")
@Log(title = "规则管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
@ApiOperation("删除规则")
public AjaxResult remove(@PathVariable Integer[] ids) {
return toAjax(ruleService.deleteRuleByIds(ids));
}
}

View File

@@ -87,9 +87,12 @@ public class BehaviortreeProcessor {
// 插入节点 treenodeinstance
Map<String, Treenodeinstance> instanceKeyMap = new HashMap<>();
Map<String, Long> nodeKeyIndexMap = new HashMap<>();
Map<String,GraphNode> nodesMap = new HashMap<>();
if (graph.hasNodes()) {
Long index = 0L;
for (GraphNode node : graph.getNodes()) {
nodesMap.put(node.getKey(), node);
Treenodeinstance instance = createNodeInstance(behaviortree, node);
treenodeinstanceService.insertTreenodeinstance(instance);
instanceKeyMap.put(node.getKey(), instance);
@@ -110,16 +113,18 @@ public class BehaviortreeProcessor {
// 插入连线 nodeconnection
if (graph.hasEdges()) {
for (GraphEdge edge : graph.getEdges()) {
Nodeconnection connection = createConnection(behaviortree, edge, instanceKeyMap, nodeKeyIndexMap);
Nodeconnection connection = createConnection(behaviortree, edge, nodesMap, instanceKeyMap, nodeKeyIndexMap);
nodeconnectionService.insertNodeconnection(connection);
}
}
}
private Nodeconnection createConnection(Behaviortree behaviortree, GraphEdge edge,
Map<String,GraphNode> nodesMap,
Map<String, Treenodeinstance> instanceKeyMap,
Map<String, Long> nodeKeyIndexMap) {
Nodeconnection connection = new Nodeconnection();
Long orderIndex = 0L;
connection.setTreeId(behaviortree.getId());
if (null != instanceKeyMap.get(edge.getSource().getCell())) {
Treenodeinstance parent = instanceKeyMap.get(edge.getSource().getCell());
@@ -129,9 +134,17 @@ public class BehaviortreeProcessor {
Treenodeinstance children = instanceKeyMap.get(edge.getTarget().getCell());
connection.setChildNodeId(children.getId());
}
if (null != nodeKeyIndexMap.get(edge.getSource().getCell())) {
connection.setOrderIndex(nodeKeyIndexMap.get(edge.getSource().getCell()));
if (null != nodesMap.get(edge.getTarget().getCell())) {
orderIndex = nodesMap.get(edge.getTarget().getCell()).getOrder();
}
if(null == orderIndex){
orderIndex = 0L;
}
// if (null != nodeKeyIndexMap.get(edge.getSource().getCell())) {
// connection.setOrderIndex(nodeKeyIndexMap.get(edge.getSource().getCell()));
// }
connection.setOrderIndex(orderIndex);
return connection;
}
@@ -157,4 +170,5 @@ public class BehaviortreeProcessor {
return instance;
}
}

View File

@@ -23,6 +23,8 @@ public class GraphNode implements Serializable {
private String type;
private Long order;
private String key;
private String name;
@@ -139,4 +141,12 @@ public class GraphNode implements Serializable {
this.variables = variables;
}
public Long getOrder() {
return order;
}
public void setOrder(Long order) {
this.order = order;
}
}

View File

@@ -1,6 +1,7 @@
package com.solution.system.mapper;
import java.util.List;
import com.solution.system.domain.Behaviortree;
/**

View File

@@ -1,6 +1,7 @@
package com.solution.system.service;
import java.util.List;
import com.solution.system.domain.Behaviortree;
/**

View File

@@ -1,6 +1,9 @@
package com.solution.system.service.impl;
import java.util.List;
import cn.hutool.core.util.ObjectUtil;
import com.solution.common.constant.ExceptionConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.solution.system.mapper.BehaviortreeMapper;

View File

@@ -55,6 +55,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</trim>
</insert>
<insert id="insert" parameterType="com.solution.scene.domain.AfsimScenario">
INSERT INTO afsim_scenario (name, description, scenario_path, communication_graph)
VALUES (#{name}, #{description}, #{scenarioPath}, #{communicationGraph})
</insert>
<update id="updateBehaviortree" parameterType="Behaviortree">
update behaviortree
<trim prefix="SET" suffixOverrides=",">

View File

@@ -18,7 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select id, template_id, param_key, data_type, default_value, description, template_type from templateparameterdef
</sql>
<select id="selectTemplateparameterdefList" parameterType="Templateparameterdef" resultMap="TemplateparameterdefResult">
<select id="selectTemplateparameterdefList" parameterType="templateparameterdef" resultMap="TemplateparameterdefResult">
<include refid="selectTemplateparameterdefVo"/>
<where>
<if test="templateId != null "> and template_id = #{templateId}</if>

View File

@@ -13,4 +13,6 @@ public class ExceptionConstants {
public static final String NOT_FOUND_F22_COMPONENT = "未找到 F-22 组件";
public static final String SCENE_CONFIG_NOT_NULL = "场景配置不能为空";
}

View File

@@ -3,5 +3,7 @@ package com.solution.common.constant;
public class PlatformAndModuleConstants {
public static final String F22 = "f22";
public static final String RED_NEBO_M_1 = "red_nebo_m_1";
public static final String RED_NEBO_M_2 = "red_nebo_m_2";
}

View File

@@ -0,0 +1,17 @@
package com.solution.rule.domain;
import com.solution.rule.domain.dto.WeaponModelDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
public class FireRuleExecuteDTO {
@ApiModelProperty("场景类型")
private Integer sceneType;
@ApiModelProperty("武器模型数据")
private List<WeaponModelDTO> weaponModelDTOs;
}

View File

@@ -0,0 +1,56 @@
package com.solution.rule.domain;
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import java.io.Serializable;
import java.util.List;
public class Platform implements Serializable {
private Integer id;
private String name;
private String description;
private List<PlatformComponent> components;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<PlatformComponent> getComponents() {
return components;
}
public void setComponents(List<PlatformComponent> components) {
this.components = components;
}
}

View File

@@ -0,0 +1,37 @@
package com.solution.rule.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("红蓝对抗规则")
public class Rule {
@ApiModelProperty("规则ID")
private Integer id;
@ApiModelProperty("规则名称")
private String name;
@ApiModelProperty("场景类型0-防御1-空降null表示通用")
private Integer sceneType;
@ApiModelProperty("触发条件JSON格式")
private String conditions;
@ApiModelProperty("响应动作JSON格式")
private String actions;
@ApiModelProperty("优先级(数值越小优先级越高)")
private Integer priority;
@ApiModelProperty("是否启用0禁用1启用")
private Boolean enabled;
@ApiModelProperty("创建时间")
private Date createdTime;
@ApiModelProperty("更新时间")
private Date updatedTime;
}

View File

@@ -0,0 +1,22 @@
package com.solution.rule.domain;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.ComponentCountVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import com.solution.rule.domain.vo.WeaponModelVO;
import lombok.Data;
import java.util.List;
@Data
public class RuleParam {
private List<PlatformWeaponAggregateVO> resultWeapons;
private WeaponModelVO weaponModelVO;
private List<WeaponModelDTO> weaponModelDTOList;
private List<ComponentCountVO> databaseWeapons;
}

View File

@@ -14,4 +14,7 @@ public class PlatformComponentNamesVO {
@ApiModelProperty("该平台下的组件名称列表(去重)")
private List<String> componentNames;
@ApiModelProperty("平台描述")
private String platformDescription;
}

View File

@@ -1,5 +1,6 @@
package com.solution.rule.domain.vo;
import com.solution.rule.domain.PlatformComponent;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -14,5 +15,8 @@ public class PlatformWeaponAggregateVO {
private String platformName;
@ApiModelProperty("该平台下的组件列表")
private List<ComponentCountVO> components;
private List<PlatformComponent> components;
@ApiModelProperty("返回数据库数据")
private List<ComponentCountVO> componentCountVOS;
}

View File

@@ -1,6 +1,5 @@
package com.solution.rule.domain.vo;
import com.solution.rule.domain.dto.WeaponModelDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

View File

@@ -0,0 +1,80 @@
package com.solution.rule.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.common.constant.PlatformAndModuleConstants;
import com.solution.rule.domain.PlatformComponent;
import com.solution.rule.domain.RuleParam;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.ComponentCountVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 战斗机处理链
* 规则:针对 F22 平台后端返回的组件数量比前端传递的大1
* 如果数据库中 F22 平台所有组件总数小于前端数量+1则返回该总数。
*/
@Component
public class WarplaneHandler extends AbstractRuleChainHandler {
// 组件数量增量常量
private static final long COMPONENT_COUNT_INCREMENT = 1;
@Override
public RuleParam doHandler(RuleParam ruleParam) {
// 1. 参数校验
if (ObjectUtil.isEmpty(ruleParam) || CollUtil.isEmpty(ruleParam.getWeaponModelDTOList())) {
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
List<WeaponModelDTO> dtoList = ruleParam.getWeaponModelDTOList();
List<ComponentCountVO> databaseWeapons = ruleParam.getDatabaseWeapons();
List<PlatformWeaponAggregateVO> resultWeapons = new ArrayList<>();
//TODO获取所有组件以及count
Iterator<WeaponModelDTO> iterator = dtoList.iterator();
while (iterator.hasNext()) {
WeaponModelDTO dto = iterator.next();
if(PlatformAndModuleConstants.RED_NEBO_M_1.equals(dto.getName())){
List<PlatformComponent> components = dto.getComponents();
List<PlatformComponent> componentList = new ArrayList<>();
//遍历前端数据的组件
for (PlatformComponent component : components) {
//遍历数据库数据
for (ComponentCountVO databaseWeapon : databaseWeapons) {
if(component.getName().equals(databaseWeapon.getComponentName())){
PlatformComponent component1 = new PlatformComponent();
component1.setName(databaseWeapon.getComponentName());
if(databaseWeapon.getCount() > component.getNum()){
component1.setNum(component.getNum() + COMPONENT_COUNT_INCREMENT);
}else {
component1.setNum(databaseWeapon.getCount());
}
//TODO 补充基本信息 暂未完成
componentList.add(component1);
}
}
}
PlatformWeaponAggregateVO platformVO = new PlatformWeaponAggregateVO();
platformVO.setPlatformName(dto.getName());
platformVO.setComponents(componentList);
resultWeapons.add(platformVO);
iterator.remove();
}
}
ruleParam.setResultWeapons(resultWeapons);
return ruleParam;
// return super.doNextHandler(ruleParam);
}
}

View File

@@ -0,0 +1,45 @@
package com.solution.rule.mapper;
import com.solution.rule.domain.Platform;
import com.solution.rule.domain.PlatformComponent;
import com.solution.rule.domain.vo.ComponentCountVO;
import com.solution.rule.domain.vo.PlatformComponentNamesVO;
import com.solution.rule.domain.vo.WeaponModelVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface FireRuleMapper {
/**
* 获取所有武器平台和组件
* @return
*/
List<WeaponModelVO> getWeapon();
List<WeaponModelVO> getPlatformComponentNames();
/**
* 获取所有组件以及数量
* @return
*/
List<ComponentCountVO> getModuleAndCount();
/**
* 获取通信组件的所有平台和组件
* @param scenarioId
* @return
*/
List<PlatformComponentNamesVO> getCommPlatformComponentNames(Integer scenarioId);
/**
* 根据平台id获取平台下所有组件
* @param platformId
* @return
*/
List<PlatformComponent> getComponents(Integer platformId);
List<Platform> findPlatformComponents(Integer scenarioId);
}

View File

@@ -0,0 +1,38 @@
package com.solution.rule.mapper;
import com.solution.rule.domain.Rule;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface RuleMapper {
/**
* 根据ID查询规则
*/
Rule selectRuleById(Integer id);
/**
* 查询规则列表(支持分页)
*/
List<Rule> selectRuleList(Rule rule);
/**
* 新增规则
*/
int insertRule(Rule rule);
/**
* 修改规则
*/
int updateRule(Rule rule);
/**
* 删除规则
*/
int deleteRuleById(Integer id);
/**
* 批量删除规则
*/
int deleteRuleByIds(@Param("ids")Integer[] ids);
}

View File

@@ -0,0 +1,48 @@
package com.solution.rule.service;
import com.solution.rule.domain.FireRuleExecuteDTO;
import com.solution.rule.domain.Platform;
import com.solution.rule.domain.PlatformComponent;
import com.solution.rule.domain.vo.PlatformComponentNamesVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import java.util.List;
public interface FireRuleService {
/**
* 开始执行规则匹配
* @param fireRuleExecuteDTO
* @return
*/
List<PlatformWeaponAggregateVO> execute(FireRuleExecuteDTO fireRuleExecuteDTO);
List<PlatformWeaponAggregateVO> getWeapon();
/**
* 获取所有武器平台和组件
* @return
*/
List<PlatformComponentNamesVO> getPlatformComponentNames();
/**
* 获取通信组件的所有平台和组件
* @param scenarioId
* @return
*/
List<PlatformComponentNamesVO> getCommPlatformComponentNames(Integer scenarioId);
/**
* 根据平台id获取平台下所有组件
* @param platformId
* @return
*/
List<PlatformComponent> getComponents(Integer platformId);
/**
* 根据场景id获取所有平台及其组件
* @param scenarioId
* @return
*/
List<Platform> findPlatformComponents(Integer scenarioId);
}

View File

@@ -0,0 +1,36 @@
package com.solution.rule.service;
import com.solution.rule.domain.Rule;
import java.util.List;
public interface IRuleService {
/**
* 根据ID查询规则
*/
Rule selectRuleById(Integer id);
/**
* 查询规则列表
*/
List<Rule> selectRuleList(Rule rule);
/**
* 新增规则
*/
int insertRule(Rule rule);
/**
* 修改规则
*/
int updateRule(Rule rule);
/**
* 删除规则
*/
int deleteRuleById(Integer id);
/**
* 批量删除规则
*/
int deleteRuleByIds(Integer[] ids);
}

View File

@@ -3,33 +3,40 @@ package com.solution.rule.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.rule.domain.FireRuleExecuteDTO;
import com.solution.rule.domain.Platform;
import com.solution.rule.domain.PlatformComponent;
import com.solution.rule.domain.RuleParam;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.ComponentCountVO;
import com.solution.rule.domain.vo.PlatformComponentNamesVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import com.solution.rule.domain.vo.WeaponModelVO;
import com.solution.rule.mapper.RuleMapper;
import com.solution.rule.service.RuleService;
import com.solution.rule.mapper.FireRuleMapper;
import com.solution.rule.service.FireRuleService;
import com.solution.rule.strategy.SceneStrategy;
import com.solution.rule.strategy.SceneStrategyFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class RuleServiceImpl implements RuleService {
public class FireRuleServiceImpl implements FireRuleService {
private static final long COMPONENT_QUANTITY_THRESHOLD = 1;
@Autowired
private SceneStrategyFactory strategyFactory;
@Autowired
private RuleMapper ruleMapper;
private FireRuleMapper ruleMapper;
@Override
/* @Override
public WeaponModelVO execute(Integer sceneType, WeaponModelDTO weaponModelDTO) {
if(ObjectUtil.isNull(sceneType) || ObjectUtil.isEmpty(weaponModelDTO)){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
@@ -43,12 +50,33 @@ public class RuleServiceImpl implements RuleService {
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
}
return result;
}
}*/
@Override
public List<PlatformWeaponAggregateVO> getWeapon() {
public List<PlatformWeaponAggregateVO> execute(FireRuleExecuteDTO fireRuleExecuteDTO) {
if(ObjectUtil.isEmpty(fireRuleExecuteDTO)){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
List<WeaponModelDTO> weaponModelDTOs = fireRuleExecuteDTO.getWeaponModelDTOs();
Integer sceneType = fireRuleExecuteDTO.getSceneType();
// 查数据库获取我方装备
List<ComponentCountVO> weapon = this.getModuleAndCount();
// 创建RuleParam并设置数据
RuleParam ruleParam = new RuleParam();
ruleParam.setWeaponModelDTOList(weaponModelDTOs);
ruleParam.setDatabaseWeapons(weapon);
// 执行策略
SceneStrategy strategy = strategyFactory.getStrategy(sceneType);
List<PlatformWeaponAggregateVO> result = strategy.execute(ruleParam);
return result;
}
@Override
public List<PlatformWeaponAggregateVO> getWeapon() {
List<WeaponModelVO> flatList = ruleMapper.getWeapon();
if (CollUtil.isEmpty(flatList)) {
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
@@ -70,7 +98,7 @@ public class RuleServiceImpl implements RuleService {
return comp;
})
.collect(Collectors.toList());
platformVO.setComponents(components);
platformVO.setComponentCountVOS(components);
result.add(platformVO);
}
@@ -106,4 +134,46 @@ public class RuleServiceImpl implements RuleService {
})
.collect(Collectors.toList());
}
/**
* 获取通信组件的所有平台和组件
* @param scenarioId
* @return
*/
@Override
public List<PlatformComponentNamesVO> getCommPlatformComponentNames(Integer scenarioId) {
return ruleMapper.getCommPlatformComponentNames(scenarioId);
}
/**
* 根据平台id获取平台下所有组件
* @param platformId
* @return
*/
@Override
public List<PlatformComponent> getComponents(Integer platformId) {
return ruleMapper.getComponents(platformId);
}
/**
* 获取所有组件以及数量
* @return
*/
private List<ComponentCountVO> getModuleAndCount(){
List<ComponentCountVO> componentCountVOS = ruleMapper.getModuleAndCount();
if(CollUtil.isEmpty(componentCountVOS)){
return new ArrayList<>();
}
return componentCountVOS;
}
@Override
public List<Platform> findPlatformComponents(Integer scenarioId) {
List<Platform> platforms = ruleMapper.findPlatformComponents(scenarioId);
if(!CollUtil.isEmpty(platforms)){
return platforms;
}
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,46 @@
package com.solution.rule.service.impl;
import com.solution.rule.domain.Rule;
import com.solution.rule.mapper.RuleMapper;
import com.solution.rule.service.IRuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RuleServiceImpl implements IRuleService {
@Autowired
private RuleMapper ruleMapper;
@Override
public Rule selectRuleById(Integer id) {
return ruleMapper.selectRuleById(id);
}
@Override
public List<Rule> selectRuleList(Rule rule) {
return ruleMapper.selectRuleList(rule);
}
@Override
public int insertRule(Rule rule) {
return ruleMapper.insertRule(rule);
}
@Override
public int updateRule(Rule rule) {
return ruleMapper.updateRule(rule);
}
@Override
public int deleteRuleById(Integer id) {
return ruleMapper.deleteRuleById(id);
}
@Override
public int deleteRuleByIds(Integer[] ids) {
return ruleMapper.deleteRuleByIds(ids);
}
}

View File

@@ -1,15 +1,17 @@
package com.solution.rule.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.rule.domain.RuleParam;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.WeaponModelVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import com.solution.rule.enums.SceneType;
import com.solution.rule.handler.RuleChainHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class AirborneStrategy implements SceneStrategy{
@@ -19,21 +21,20 @@ public class AirborneStrategy implements SceneStrategy{
private RuleChainHandler ruleChainHandler;
/**
* 空降场景处理
* @param weaponModelDTO
* @param ruleParam
* @return
*/
@Override
public WeaponModelVO execute(WeaponModelDTO weaponModelDTO) {
if(ObjectUtil.isEmpty(weaponModelDTO)){
public List<PlatformWeaponAggregateVO> execute(RuleParam ruleParam) {
if(ObjectUtil.isEmpty(ruleParam) || CollUtil.isEmpty(ruleParam.getWeaponModelDTOList())){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
RuleParam ruleParam = new RuleParam();
ruleParam.setWeaponModelDTO(weaponModelDTO);
ruleParam = ruleChainHandler.findRuleParam(ruleParam);
if(ObjectUtil.isEmpty(ruleParam.getWeaponModelVO())){
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
}
return ruleParam.getWeaponModelVO();
return ruleParam.getResultWeapons();
}
@Override

View File

@@ -1,15 +1,17 @@
package com.solution.rule.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.rule.domain.RuleParam;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.WeaponModelVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import com.solution.rule.enums.SceneType;
import com.solution.rule.handler.RuleChainHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class DefenseStrategy implements SceneStrategy{
@@ -18,21 +20,21 @@ public class DefenseStrategy implements SceneStrategy{
private RuleChainHandler ruleChainHandler;
/**
* 防御场景处理
* @param weaponModelDTO
* @param
* @return
*/
@Override
public WeaponModelVO execute(WeaponModelDTO weaponModelDTO) {
if(ObjectUtil.isEmpty(weaponModelDTO)){
public List<PlatformWeaponAggregateVO> execute(RuleParam ruleParam) {
if(ObjectUtil.isEmpty(ruleParam) || CollUtil.isEmpty(ruleParam.getWeaponModelDTOList())){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
RuleParam ruleParam = new RuleParam();
ruleParam.setWeaponModelDTO(weaponModelDTO);
ruleParam = ruleChainHandler.findRuleParam(ruleParam);
if(ObjectUtil.isEmpty(ruleParam.getWeaponModelVO())){
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
}
return ruleParam.getWeaponModelVO();
return ruleParam.getResultWeapons();
}
@Override

View File

@@ -0,0 +1,15 @@
package com.solution.rule.strategy;
import com.solution.rule.domain.RuleParam;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import com.solution.rule.enums.SceneType;
import java.util.HashMap;
import java.util.List;
public interface SceneStrategy {
List<PlatformWeaponAggregateVO> execute(RuleParam ruleParam);
SceneType getSceneType();
}

View File

@@ -0,0 +1,109 @@
<?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.FireRuleMapper">
<resultMap id="platformComponentCountMap" type="com.solution.rule.domain.vo.WeaponModelVO">
<result property="platformName" column="platform_name"/>
<result property="componentName" column="component_name"/>
<result property="count" column="count"/>
</resultMap>
<resultMap id="PlatformComponentNamesVOResultMap" type="com.solution.rule.domain.vo.PlatformComponentNamesVO">
<result property="platformName" column="platformName"/>
<result property="platformDescription" column="description"/>
<collection property="componentNames" ofType="java.lang.String">
<result column="componentName"/>
</collection>
</resultMap>
<select id="getWeapon" resultMap="platformComponentCountMap">
SELECT
p.name AS platform_name,
pc.name AS component_name,
COUNT(pc.name) AS count
FROM platform p
LEFT JOIN platform_component pc ON p.id = pc.platform_id
GROUP BY p.id, p.name, pc.name
ORDER BY p.id, pc.name
</select>
<select id="getPlatformComponentNames" resultType="com.solution.rule.domain.vo.WeaponModelVO">
SELECT DISTINCT
p.name AS platformName,
pc.name AS componentName
FROM platform p
INNER JOIN platform_component pc ON p.id = pc.platform_id
WHERE pc.name IS NOT NULL
ORDER BY p.name, pc.name
</select>
<select id="getModuleAndCount" resultType="com.solution.rule.domain.vo.ComponentCountVO">
SELECT
name AS componentName,
COUNT(*) AS count
FROM platform_component
WHERE name IS NOT NULL
GROUP BY name
ORDER BY count DESC;
</select>
<select id="getCommPlatformComponentNames" resultMap="PlatformComponentNamesVOResultMap"
parameterType="java.lang.Integer">
SELECT
p.name AS platformName,
pc.name AS componentName,
p.description AS description
FROM platform p
INNER JOIN platform_component pc ON p.id = pc.platform_id
WHERE pc.type = 'comm'
AND p.scenario_id = #{scenarioId}
</select>
<select id="getComponents" resultType="com.solution.rule.domain.PlatformComponent"
parameterType="java.lang.Integer">
SELECT id,name,type,description,platform_id
FROM platform_component
WHERE platform_id = #{platformId}
</select>
<resultMap id="VPlatformComponentMap" type="com.solution.rule.domain.PlatformComponent">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="type" column="type"/>
<result property="description" column="description"/>
<result property="platformId" column="platform_id"/>
<result property="num" column="num"/>
</resultMap>
<select id="findComponentsByPlatformId" resultMap="VPlatformComponentMap">
SELECT * FROM platform_component
WHERE platform_id=#{platformId}
</select>
<resultMap id="VPlatformMap" type="com.solution.rule.domain.Platform">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="description" column="description"/>
<collection property="components"
ofType="com.solution.rule.domain.PlatformComponent"
column="id"
select="findComponentsByPlatformId"/>
</resultMap>
<select id="findPlatformComponents" resultMap="VPlatformMap"
parameterType="java.lang.Integer">
SELECT
p.*,
pc.*
FROM platform p
LEFT JOIN platform_component pc ON p.id = pc.platform_id
WHERE pc.type = "comm"
AND p.scenario_id = #{scenarioId}
GROUP BY p.id;
</select>
</mapper>

View File

@@ -0,0 +1,97 @@
<?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.RuleMapper">
<resultMap id="RuleResult" type="com.solution.rule.domain.Rule">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sceneType" column="scene_type"/>
<result property="conditions" column="conditions"/>
<result property="actions" column="actions"/>
<result property="priority" column="priority"/>
<result property="enabled" column="enabled"/>
<result property="createdTime" column="created_time"/>
<result property="updatedTime" column="updated_time"/>
</resultMap>
<sql id="selectRuleVo">
select id, name, scene_type, conditions, actions, priority, enabled, created_time, updated_time
from rule
</sql>
<select id="selectRuleById" parameterType="Integer" resultMap="RuleResult">
<include refid="selectRuleVo"/>
where id = #{id}
</select>
<select id="selectRuleList" parameterType="com.solution.rule.domain.Rule" resultMap="RuleResult">
<include refid="selectRuleVo"/>
<where>
<if test="name != null and name != ''">
AND name like concat('%', #{name}, '%')
</if>
<if test="sceneType != null">
AND scene_type = #{sceneType}
</if>
<if test="enabled != null">
AND enabled = #{enabled}
</if>
</where>
</select>
<insert id="insertRule" parameterType="com.solution.rule.domain.Rule" useGeneratedKeys="true" keyProperty="id">
insert into rule
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">name,</if>
<if test="sceneType != null">scene_type,</if>
<if test="conditions != null">conditions,</if>
<if test="actions != null">actions,</if>
<if test="priority != null">priority,</if>
<if test="enabled != null">enabled,</if>
created_time,
updated_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">#{name},</if>
<if test="sceneType != null">#{sceneType},</if>
<if test="conditions != null">#{conditions},</if>
<if test="actions != null">#{actions},</if>
<if test="priority != null">#{priority},</if>
<if test="enabled != null">#{enabled},</if>
now(),
now()
</trim>
</insert>
<update id="updateRule" parameterType="com.solution.rule.domain.Rule">
update rule
<set>
<if test="name != null and name != ''">name = #{name},</if>
<if test="sceneType != null">scene_type = #{sceneType},</if>
<if test="conditions != null">conditions = #{conditions},</if>
<if test="actions != null">actions = #{actions},</if>
<if test="priority != null">priority = #{priority},</if>
<if test="enabled != null">enabled = #{enabled},</if>
updated_time = now()
</set>
where id = #{id}
</update>
<delete id="deleteRuleById" parameterType="Integer">
delete from rule where id = #{id}
</delete>
<!--<delete id="deleteRuleByIds" parameterType="String">
delete from rule where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>-->
<delete id="deleteRuleByIds">
delete from rule where id in
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>solution</artifactId>
<groupId>com.solution</groupId>
<version>3.9.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>solution-scene</artifactId>
<description>
scene模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.solution</groupId>
<artifactId>solution-common</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,51 @@
package com.solution.scene.controller;
import com.solution.common.annotation.Log;
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.scene.domain.AfsimScenario;
import com.solution.scene.service.SceneService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 场景管理
*/
@Api("场景管理")
@RestController
@RequestMapping("/api/system/scene")
public class SceneController extends BaseController {
@Autowired
private SceneService sceneService;
/**
* 保存场景配置
*/
@ApiOperation("保存场景配置")
@PostMapping("/saveSceneConfig")
@Log(title = "行为树主", businessType = BusinessType.INSERT)
public AjaxResult saveSceneConfig(@RequestBody AfsimScenario afsimScenario)
{
return toAjax(sceneService.saveOrUpdate(afsimScenario));
}
/**
* 获取场景列表
* @return
*/
@GetMapping("/list")
@ApiOperation("获取场景列表")
public TableDataInfo list(){
startPage();
List<AfsimScenario> list = sceneService.selectSceneList();
return getDataTable(list);
}
}

View File

@@ -0,0 +1,64 @@
package com.solution.scene.domain;
/**
* 场景配置表
* 对应表 afsim_scenario
*/
public class AfsimScenario {
private Integer id;
private String name;
private String description;
private String scenarioPath;
private String communicationGraph; // 用于存储场景中的通讯关系
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getScenarioPath() {
return scenarioPath;
}
public void setScenarioPath(String scenarioPath) {
this.scenarioPath = scenarioPath;
}
public String getCommunicationGraph() {
return communicationGraph;
}
public void setCommunicationGraph(String communicationGraph) {
this.communicationGraph = communicationGraph;
}
@Override
public String toString() {
return "AfsimScenario{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
", scenarioPath='" + scenarioPath + '\'' +
", communicationGraph='" + communicationGraph + '\'' +
'}';
}
}

View File

@@ -0,0 +1,26 @@
package com.solution.scene.mapper;
import com.solution.scene.domain.AfsimScenario;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SceneMapper {
/**
* 保存场景配置
* @param afsimScenario
* @return
*/
int insert(AfsimScenario afsimScenario);
int update(AfsimScenario afsimScenario);
/**
* 获取场景列表
* @return
*/
List<AfsimScenario> selectSceneList();
}

View File

@@ -0,0 +1,25 @@
package com.solution.scene.service;
import com.solution.scene.domain.AfsimScenario;
import java.util.List;
public interface SceneService {
/**
* 保存场景配置
* @param afsimScenario
* @return
*/
int insert(AfsimScenario afsimScenario);
int update(AfsimScenario afsimScenario);
int saveOrUpdate(AfsimScenario afsimScenario);
/**
* 获取场景列表
* @return
*/
List<AfsimScenario> selectSceneList();
}

View File

@@ -0,0 +1,44 @@
package com.solution.scene.service.impl;
import com.solution.scene.domain.AfsimScenario;
import com.solution.scene.mapper.SceneMapper;
import com.solution.scene.service.SceneService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SceneServiceImpl implements SceneService {
@Autowired
private SceneMapper sceneMapper;
@Override
public int insert(AfsimScenario afsimScenario) {
return sceneMapper.insert(afsimScenario);
}
@Override
public int update(AfsimScenario afsimScenario) {
return sceneMapper.update(afsimScenario);
}
@Override
public int saveOrUpdate(AfsimScenario afsimScenario) {
if (null != afsimScenario.getId() && afsimScenario.getId() > 0) {
return sceneMapper.update(afsimScenario);
}
return insert(afsimScenario);
}
/**
* 获取场景列表
* @return
*/
@Override
public List<AfsimScenario> selectSceneList() {
return sceneMapper.selectSceneList();
}
}

View File

@@ -0,0 +1,33 @@
<?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.scene.mapper.SceneMapper">
<resultMap id="SceneMap" type="com.solution.scene.domain.AfsimScenario">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="description" column="description" />
<result property="scenarioPath" column="scenario_path" />
<result property="communicationGraph" column="communication_graph" />
</resultMap>
<insert id="insert" parameterType="com.solution.scene.domain.AfsimScenario">
INSERT INTO afsim_scenario (name, description, scenario_path, communication_graph)
VALUES (#{name}, #{description}, #{scenarioPath}, #{communicationGraph})
</insert>
<select id="selectSceneList" resultMap="SceneMap">
SELECT id, name, description, scenario_path, communication_graph FROM afsim_scenario
</select>
<insert id="update" parameterType="com.solution.scene.domain.AfsimScenario">
update afsim_scenario
set name=#{name},
description=#{description},
scenario_path=#{scenarioPath},
communication_graph=#{communicationGraph}
where id=#{id}
</insert>
</mapper>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773366372468" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13681" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M752.5888 714.0864a81.3568 81.3568 0 0 0-18.944 2.2016l-50.688-77.9776a160.3072 160.3072 0 0 0-13.3632-245.76l42.0352-82.9952a76.1856 76.1856 0 0 0 10.24 0.6656 81.92 81.92 0 1 0-59.2896-25.6L620.544 367.616a160.4608 160.4608 0 0 0-206.08 134.4l-93.2864 5.12a81.92 81.92 0 1 0 2.9184 54.8864l93.2864-5.12a160.4608 160.4608 0 0 0 219.4432 111.5136l50.688 77.8752a81.92 81.92 0 1 0 65.0752-32.1536z" fill="#ffffff" p-id="13682"></path></svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773366567190" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18046" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M303.146667 648.96A128.042667 128.042667 0 1 1 213.333333 647.253333V376.746667a128.042667 128.042667 0 1 1 85.333334 0V512c35.669333-26.794667 79.957333-42.666667 128-42.666667h170.666666a128.042667 128.042667 0 0 0 123.52-94.293333 128.042667 128.042667 0 1 1 86.698667 2.730667A213.418667 213.418667 0 0 1 597.333333 554.666667h-170.666666a128.042667 128.042667 0 0 0-123.52 94.293333zM256 725.333333a42.666667 42.666667 0 1 0 0 85.333334 42.666667 42.666667 0 0 0 0-85.333334zM256 213.333333a42.666667 42.666667 0 1 0 0 85.333334 42.666667 42.666667 0 0 0 0-85.333334z m512 0a42.666667 42.666667 0 1 0 0 85.333334 42.666667 42.666667 0 0 0 0-85.333334z" fill="#ffffff" p-id="18047"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773366469175" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14793" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M298.666667 768h298.666666v-128a85.333333 85.333333 0 0 1 85.333334-85.333333h128V256a85.333333 85.333333 0 0 1-85.333334-85.333333h-298.666666v128a85.333333 85.333333 0 0 1-85.333334 85.333333H85.333333a85.333333 85.333333 0 0 1-85.333333-85.333333V128a85.333333 85.333333 0 0 1 85.333333-85.333333h256a85.333333 85.333333 0 0 1 85.333334 85.333333h310.101333A85.333333 85.333333 0 1 1 853.333333 244.565333V554.666667h85.333334a85.333333 85.333333 0 0 1 85.333333 85.333333v170.666667a85.333333 85.333333 0 0 1-85.333333 85.333333h-256a85.333333 85.333333 0 0 1-85.333334-85.333333H287.232A85.333333 85.333333 0 1 1 170.666667 694.101333V469.333333H106.666667a21.333333 21.333333 0 1 1 0-42.666666h213.333333a21.333333 21.333333 0 1 1 0 42.666666H213.333333v213.333334a85.333333 85.333333 0 0 1 85.333334 85.333333zM85.333333 85.333333a42.666667 42.666667 0 0 0-42.666666 42.666667v170.666667a42.666667 42.666667 0 0 0 42.666666 42.666666h256a42.666667 42.666667 0 0 0 42.666667-42.666666V128a42.666667 42.666667 0 0 0-42.666667-42.666667H85.333333z m597.333334 512a42.666667 42.666667 0 0 0-42.666667 42.666667v170.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h256a42.666667 42.666667 0 0 0 42.666666-42.666666v-170.666667a42.666667 42.666667 0 0 0-42.666666-42.666667h-256z m21.333333 341.333334h213.333333a21.333333 21.333333 0 1 1 0 42.666666h-213.333333a21.333333 21.333333 0 1 1 0-42.666666zM810.666667 213.333333a42.666667 42.666667 0 1 0 0-85.333333 42.666667 42.666667 0 0 0 0 85.333333zM213.333333 810.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" fill="#e6e6e6" fill-opacity=".85" p-id="14794"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773366535929" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16891" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M535.104 719.168l-0.064-416.256c0-19.84-10.368-23.488-23.104-23.488-12.736 0-23.104 3.584-23.04 23.488v416.256c0 19.968 10.304 25.344 23.104 25.536 12.672-0.128 23.04-5.568 23.104-25.536z" p-id="16892" fill="#ffffff"></path><path d="M304.768 535.104l416.256-0.064c20.032 0.192 23.488-10.304 23.488-22.976 0-12.928-3.648-23.232-23.488-23.232h-416.256c-20.032 0-25.536 10.368-25.344 23.104 0 12.8 5.312 23.168 25.344 23.168z" p-id="16893" fill="#ffffff"></path><path d="M999.104 467.776l-442.88-442.88a63.68 63.68 0 0 0-90.112 0l-441.216 441.344a63.616 63.616 0 0 0 0 90.048l443.008 442.88a63.744 63.744 0 0 0 90.048 0l441.216-441.216a63.808 63.808 0 0 0-0.064-90.176z m-446.08 466.304a56.96 56.96 0 0 1-80.512 0l-382.592-382.592a56.832 56.832 0 0 1 0-80.384l380.928-381.12a57.088 57.088 0 0 1 80.576-0.064l382.72 382.784a56.832 56.832 0 0 1-0.064 80.32l-381.056 381.056z" fill="#ffffff" p-id="16894"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773366601535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19126" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M513.6 417.1c41.5-23 83.2-45.6 124.6-69 14.6-8.3 26.5-7.4 38.8 4.6 17.4 16.9 37.6 29.7 60.5 38.1 112.8 41.2 235.6-54.1 223.7-173.5-9.9-99-99.7-169.2-196.2-153.4C673 79 611.4 167.2 627.7 260.7c4.4 25.4 3.1 28.1-19.6 40.6-39.2 21.7-78.4 43.3-117.6 64.9-21.2 11.7-26.3 10.9-42.7-6.8-62.5-67.5-159.4-89.9-245-56.6-84.6 32.9-140.4 115-140.4 206.6 0 101.9 67.3 190.5 165.9 215.2 78.8 19.8 148.8 0.2 209-54.1 11.1-10 21.1-15.5 35.3-6.2 46.8 30.8 93.8 61.2 140.7 91.7 13.8 9 18.4 21.2 15.1 37.1-2.7 13-4.7 26.3-3.4 39.7 7.5 80.1 72.3 134.5 154.6 127.5 52.4-4.5 92-31.5 113.3-79.7 20.9-47.3 19-95.1-11.7-138.4-45.3-63.9-124.9-80.1-190.9-37.5-21.6 13.9-36.9 12.8-57.2-1.5-41.8-29.5-85.2-56.8-128.5-84.1-14.3-9-18-18.6-11.7-34.4 17-43 18.8-87.1 6.1-131.7-6.4-21.6-4.7-25.2 14.6-35.9z m280.1-298c62.4 0.5 111.6 50.1 111.5 112.2-0.1 62.5-49.2 111.4-111.9 111.4-63.2 0.1-113-49.7-112.6-112.6 0.4-61.6 51.2-111.5 113-111z m-28.1 618.6c46 0.8 84.7 40.2 84 85.6-0.7 45.1-39.6 82.8-85 82.4-46.5-0.4-84.5-39-83.8-85.1 0.7-44.9 40.4-83.7 84.8-82.9z" fill="#ffffff" p-id="19127"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773366502709" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15837" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 0h1024v1024H0z" fill="#ffffff" fill-opacity="0" p-id="15838"></path><path d="M512 64a448 448 0 1 1 0 896A448 448 0 0 1 512 64z m0 64a384 384 0 1 0 0 768A384 384 0 0 0 512 128zM416.768 320c17.28 0 31.232 14.208 31.232 31.744v384.512a31.488 31.488 0 0 1-31.232 31.744 31.488 31.488 0 0 1-31.296-31.744l-0.064-311.04-76.032 77.12a30.912 30.912 0 0 1-44.16 0 32 32 0 0 1 0-44.8l126.272-128.256a30.976 30.976 0 0 1 19.136-9.152L416.768 320z m342.08 310.272l-127.36 128.192-2.56 2.304-3.008 2.176-4.8 2.56-5.44 1.728-5.376 0.768h-4.608l-5.504-0.96-4.288-1.408-4.8-2.432-2.24-1.536-3.648-3.2-3.008-3.392-3.072-5.12-1.92-5.184-0.64-2.752L576 738.368 576 351.744A32 32 0 0 1 608.32 320a32 32 0 0 1 32.32 31.744v306.56l71.872-72.32a32.768 32.768 0 0 1 45.76-0.576 31.36 31.36 0 0 1 0.576 44.864z" fill="#ffffff" p-id="15839"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773309534522" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11546" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 939.52c-14.848 0-28.672-5.632-38.912-15.872l-372.224-372.224c-21.504-21.504-21.504-56.32 0-77.824l372.224-372.224c10.24-10.24 24.064-15.872 38.912-15.872 14.848 0 28.672 5.632 38.912 15.872l372.224 372.224c21.504 21.504 21.504 56.32 0 77.824l-372.224 372.224c-10.24 9.728-24.064 15.872-38.912 15.872z m0-845.312c-12.288 0-23.552 4.608-32.256 13.312L107.52 479.744c-17.92 17.92-17.92 46.592 0 64L479.744 916.48c8.704 8.704 19.968 13.312 32.256 13.312s23.552-4.608 32.256-13.312l372.224-372.224c17.92-17.92 17.92-46.592 0-64L544.256 107.52c-8.704-8.704-19.968-13.312-32.256-13.312z" fill="#EEE" p-id="11547"></path></svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773577091908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7408" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M285.952 623.616c-94.464 0-171.264-76.8-171.264-171.264s76.8-171.264 171.264-171.264c11.008 0 21.76 1.024 32.512 3.072 16.64 3.072 27.648 19.2 24.32 35.84-3.072 16.64-19.2 27.648-35.84 24.32-6.912-1.28-13.824-2.048-20.736-2.048-60.672 0-109.824 49.152-109.824 109.824s49.152 109.824 109.824 109.824c16.896 0 30.72 13.824 30.72 30.72s-14.08 30.976-30.976 30.976z" fill="#FFFFFF" p-id="7409"></path><path d="M313.088 343.296c-13.824 0-26.624-9.472-29.952-23.552-3.84-15.872-5.632-32.256-5.632-48.896 0-116.48 94.72-211.456 211.456-211.456 55.552 0 108.032 21.504 147.712 60.16 39.68 38.912 62.208 90.624 63.488 146.176 0.512 16.896-13.056 30.976-29.952 31.488h-0.768c-16.64 0-30.208-13.312-30.72-29.952-1.792-80.64-69.12-146.432-150.016-146.432-82.688 0-150.016 67.328-150.016 150.016 0 11.776 1.28 23.296 4.096 34.816 3.84 16.384-6.4 33.024-22.784 36.864-2.304 0.512-4.608 0.768-6.912 0.768z" fill="#FFFFFF" p-id="7410"></path><path d="M702.72 623.616c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72c73.472 0 133.12-59.648 133.12-133.12s-59.648-133.12-133.12-133.12c-8.96 0-18.176 1.024-26.88 2.816-16.64 3.328-32.768-7.424-36.352-24.064-3.328-16.64 7.424-32.768 23.808-36.352 12.8-2.56 26.112-3.84 39.168-3.84 107.264 0 194.56 87.296 194.56 194.56s-87.04 194.56-194.304 194.56z" fill="#FFFFFF" p-id="7411"></path><path d="M286.976 562.176h415.744v61.44H286.976z" fill="#FFFFFF" p-id="7412"></path><path d="M272.896 794.88H209.152v-61.44h63.744c4.608 0 8.448-3.84 8.448-8.448V599.04h61.44v125.952c0 38.656-31.232 69.888-69.888 69.888zM474.112 608.256h61.44v189.44h-61.44zM837.376 807.68H734.72c-38.4 0-69.888-31.232-69.888-69.888v-129.792h61.44v129.792c0 4.608 3.84 8.448 8.448 8.448h102.656v61.44z" fill="#FFFFFF" p-id="7413"></path><path d="M142.336 875.776c-59.392 0-107.52-48.128-107.52-107.52s48.128-107.52 107.52-107.52 107.52 48.128 107.52 107.52-48.128 107.52-107.52 107.52z m0-153.6c-25.344 0-46.08 20.736-46.08 46.08s20.736 46.08 46.08 46.08 46.08-20.736 46.08-46.08-20.736-46.08-46.08-46.08zM501.76 977.152c-59.392 0-107.52-48.128-107.52-107.52s48.128-107.52 107.52-107.52 107.52 48.128 107.52 107.52-48.384 107.52-107.52 107.52z m0-153.6c-25.344 0-46.08 20.736-46.08 46.08s20.736 46.08 46.08 46.08 46.08-20.736 46.08-46.08-20.736-46.08-46.08-46.08zM888.576 881.664c-59.392 0-107.52-48.128-107.52-107.52s48.128-107.52 107.52-107.52 107.52 48.128 107.52 107.52-48.384 107.52-107.52 107.52z m0-153.6c-25.344 0-46.08 20.736-46.08 46.08s20.736 46.08 46.08 46.08 46.08-20.736 46.08-46.08-20.736-46.08-46.08-46.08z" fill="#FFFFFF" p-id="7414"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1769565073187" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3675" width="200" height="200">
<path d="M514.048 128q79.872 0 149.504 30.208t121.856 82.432 82.432 122.368 30.208 150.016q0 78.848-30.208 148.48t-82.432 121.856-121.856 82.432-149.504 30.208-149.504-30.208-121.856-82.432-82.432-121.856-30.208-148.48q0-79.872 30.208-150.016t82.432-122.368 121.856-82.432 149.504-30.208z"
p-id="3676" fill="#5da0df"></path>
p-id="3676" fill="#3c82f6"></path>
</svg>

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 624 B

View File

@@ -32,7 +32,15 @@ export const routes: RouteRecordRaw[] = [
meta: {
title: '决策树',
},
component: () => import('@/views/decision/designer.vue'),
component: () => import('@/views/decision/designer/designer.vue'),
},
{
name: 'decision-communication',
path: '/app/decision/communication',
meta: {
title: '通信',
},
component: () => import('@/views/decision/communication/communication.vue'),
},
{
name: 'decision-algorithm-management',
@@ -42,4 +50,12 @@ export const routes: RouteRecordRaw[] = [
},
component: () => import('@/views/decision/algorithm/management.vue'),
},
{
name: 'decision-fire-rule',
path: '/app/decision/fire-rule',
meta: {
title: '活力规则',
},
component: () => import('@/views/decision/rule/management.vue'),
},
]

View File

@@ -1204,7 +1204,7 @@
.ant-tabs-content {
//padding: 15px;
padding: 4px;
background: #041b36db;
background: #041832;
}
&.settings-tab,
@@ -1641,4 +1641,190 @@
color: #b5b39d;
cursor: pointer;
}
}
.ant-input-group-addon {
.anticon{
color: #eeeeee;
}
}
.ant-switch {
background: rgb(8 30 59);
}
.ks-algorithm-card {
.ant-card-head-title {
span.text {
display: block;
line-height: 30px;
}
}
}
.ks-sidebar-header {
line-height: 40px;
background: #081d36;
min-height: 40px;
background: url(@/assets/icons/card-head.png) left / 180% 100%;
padding: 0 10px;
.ks-sidebar-title {
color: #7ae8fc;
font-size: 16px;
.icon {
background: url(@/assets/icons/list.png) center / 100% 100%;
width: 25px;
height: 25px;
display: block;
margin-top: 7px;
}
.text{
margin-left: 40px;
font-size: 16px;
color: #eee;
}
}
.ks-sidebar-add {
position: absolute;
right: 7px;
top: 8px;
font-size: 12px;
.anticon {
display: block;
float: left;
line-height: 16px;
}
}
}
.ant-list {
&.ks-sidebar-list {
.ant-list-item {
cursor: pointer;
transition: all 0.5s;
border-left: 2px solid transparent;
position: relative;
&.selected,
&:hover {
background: #0a1b3c;
border-left: 2px solid #11377e;
}
}
.ks-sidebar-list-type {
position: absolute;
right: 10px;
.ant-badge {
.ant-badge-count {
color: #c3c2c2;
background: #333f7d;
box-shadow: 0 0 0 1px #325478;
}
}
}
.ant-list-item-meta {
.ant-list-item-meta-title {
color: #7ae8fc;
}
.ant-list-item-meta-description {
color: #4d8c98;
font-size: 13px;
}
}
}
}
.ks-sidebar-list-param-list {
padding: 15px;
border: 1px solid #475f71;
border-radius: 2px;
.ks-sidebar-list-param-item {
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
}
.ks-sidebar-list-param-actions {
.anticon {
color: #7ae8fc;
font-size: 20px;
display: block;
line-height: 26px;
cursor: pointer;
}
}
.ant-collapse {
.ant-list-sm {
.ant-list-item {
padding: 4px 15px;
cursor: pointer;
color: rgb(130 196 233);
position: relative;
.ks-tree-actions {
position: absolute;
right: 10px;
display: none;
}
&:hover {
background: #0d2d4e;
.ks-tree-actions {
display: block;
}
}
}
}
&.ks-trees-collapse {
.ant-collapse-content-box {
padding: 0;
height: 40vh;
position: relative;
}
}
}
.create-tree-icon {
cursor: pointer;
}
.ant-list-item {
padding: 3px 5px;
cursor: pointer;
color: rgb(130 196 233);
&:hover {
background: #0d2d4e;
}
}
.ks-model-builder-body .ks-model-builder-left .ant-collapse {
&.platform-collapse{
.ant-collapse-content-box{
max-height: 45.5vh;
}
}
}

View File

@@ -11,13 +11,13 @@
新增
</a-button>
</div>
<a-list item-layout="horizontal" :data-source="algorithms" class="ks-algorithm-list">
<a-list item-layout="horizontal" :data-source="algorithms" class="ks-sidebar-list">
<template #renderItem="{ item }">
<a-list-item @click="()=> handleSelect(item)" :class="selectedAlgorithm?.id === item.id ? 'selected' : null">
<a-list-item-meta :description="substring(item.description,20)">
<template #title>
<span class="ks-algorithm-name">{{ substring(item.name, 20) }}</span>
<span class="ks-algorithm-type"><a-badge size="small" :count="getAlgorithmTypeName(item.type)"></a-badge></span>
<span class="ks-sidebar-list-type"><a-badge size="small" :count="getAlgorithmTypeName(item.type)"></a-badge></span>
</template>
</a-list-item-meta>
</a-list-item>
@@ -95,8 +95,8 @@
:name="['algorithmParamList']"
>
<a-form-item-rest>
<div class="ks-algorithm-param-list">
<div class="ks-algorithm-param-item" v-for="(item,index) in selectedAlgorithm.algorithmParamList">
<div class="ks-sidebar-list-param-list">
<div class="ks-sidebar-list-param-item" v-for="(item,index) in selectedAlgorithm.algorithmParamList">
<a-row :gutter="15">
<a-col :span="7">
<a-input v-model:value="item.paramName" placeholder="请输入参数名" />
@@ -108,7 +108,7 @@
<a-input v-model:value="item.description" placeholder="请输入描述" />
</a-col>
<a-col :span="3">
<a-space class="ks-algorithm-param-actions">
<a-space class="ks-sidebar-list-param-actions">
<MinusCircleOutlined @click="()=> handleMinus(index)" />
<PlusCircleOutlined @click="handleAdd" v-if="index === 0" />
</a-space>
@@ -125,8 +125,8 @@
:name="['algoConfig']"
>
<a-form-item-rest>
<div class="ks-algorithm-param-list">
<div class="ks-algorithm-param-item" v-for="(t,index) in selectedAlgorithm.algoConfigList">
<div class="ks-sidebar-list-param-list">
<div class="ks-sidebar-list-param-item" v-for="(t,index) in selectedAlgorithm.algoConfigList">
<a-row :gutter="15">
<a-col :span="7">
<a-select placeholder="请选择参数" v-model:value="t.name" @change="(v: any)=> t.name = v">
@@ -139,7 +139,7 @@
</a-select>
</a-col>
<a-col :span="3">
<a-space class="ks-algorithm-param-actions">
<a-space class="ks-sidebar-list-param-actions">
<MinusCircleOutlined @click="()=> handleMinusConfig(index)" />
<PlusCircleOutlined @click="handleAddConfig" v-if="index === 0" />
</a-space>
@@ -290,6 +290,7 @@ const handleCreate = () => {
const handleSelect = (item: Algorithm) => {
selectedAlgorithm.value = resolveItem(item);
formRef.value?.resetFields();
};
const handleAdd = () => {
@@ -394,120 +395,3 @@ const handleChange = (page: number, pageSize: number) => {
onMounted(() => load());
</script>
<style lang="less">
.ks-algorithm-card {
.ant-card-head-title {
span.text {
display: block;
line-height: 30px;
}
}
}
.ks-sidebar-header {
line-height: 40px;
padding: 5px 15px;
background: #081d36;
min-height: 40px;
background: url(@/assets/icons/card-head.png) left / 180% 100%;
padding: 0 10px;
.ks-sidebar-title {
color: #7ae8fc;
font-size: 16px;
.icon {
background: url(@/assets/icons/list.png) center / 100% 100%;
width: 25px;
height: 25px;
display: block;
margin-top: 7px;
}
.text{
margin-left: 40px;
font-size: 16px;
color: #eee;
}
}
.ks-sidebar-add {
position: absolute;
right: 7px;
top: 8px;
font-size: 12px;
.anticon {
display: block;
float: left;
line-height: 16px;
}
}
}
.ant-list {
&.ks-algorithm-list {
.ant-list-item {
cursor: pointer;
transition: all 0.5s;
border-left: 2px solid transparent;
position: relative;
&.selected,
&:hover {
background: #0a1b3c;
border-left: 2px solid #11377e;
}
}
.ks-algorithm-type {
position: absolute;
right: 10px;
.ant-badge {
.ant-badge-count {
color: #c3c2c2;
background: #333f7d;
box-shadow: 0 0 0 1px #325478;
}
}
}
.ant-list-item-meta {
.ant-list-item-meta-title {
color: #7ae8fc;
}
.ant-list-item-meta-description {
color: #4d8c98;
font-size: 13px;
}
}
}
}
.ks-algorithm-param-list {
padding: 15px;
border: 1px solid #475f71;
border-radius: 2px;
.ks-algorithm-param-item {
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
}
.ks-algorithm-param-actions {
.anticon {
color: #7ae8fc;
font-size: 20px;
display: block;
line-height: 26px;
cursor: pointer;
}
}
</style>

View File

@@ -1,11 +0,0 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2025 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
export { createGraphCanvas } from './graph';
export * from './hooks';

View File

@@ -0,0 +1,32 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import { HttpRequestClient } from '@/utils/request';
import type { PlatformWithComponentsResponse, ScenarioPageableResponse, ScenarioRequest, Scenario } from './types';
import type { BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api',
});
export const findScenarioByQuery = (_query: Partial<ScenarioRequest> = {}): Promise<ScenarioPageableResponse> => {
return req.get('/system/scene/list', _query);
};
export const deleteOneScenarioById = (id: number): Promise<BasicResponse> => {
return req.delete(`/system/behaviortree/${id}`);
};
export const findPlatformWithComponents = (id: number): Promise<PlatformWithComponentsResponse> => {
return req.get(`/system/firerule/platforms/${id}`);
};
export const saveScenario = (scenario: Scenario): Promise<BasicResponse> => {
return req.postJson(`/system/scene/saveSceneConfig`,scenario);
};

View File

@@ -0,0 +1,419 @@
<template>
<Wrapper>
<a-layout class="bg-transparent" style="background: transparent">
<Header />
<a-layout class="ks-layout-body">
<div class="ks-model-builder-body">
<div class="ks-model-builder-left">
<PlatformCard
ref="scenariosCardRef"
@create="handleCreate"
@select="handleSelect"
/>
<NodesCard
v-if="currentScenario && currentScenario.id >0"
:scenario="currentScenario"
@drag-item-start="handleDragStart"
@drag-item-end="handleDragEnd"
/>
</div>
<div class="ks-model-builder-content" style="width: calc(100% - 250px);">
<div class="ks-model-builder-actions">
<a-space>
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" size="small" @click="handleSave">
<CheckOutlined />
<span>保存</span>
</a-button>
</a-space>
</div>
<!-- 画布容器添加拖放事件 -->
<div
ref="canvas"
class="ks-model-builder-canvas"
@dragenter="handleDragEnter"
@dragleave="handleDragLeave"
@drop="handleDrop"
@dragover.prevent
></div>
<TeleportContainer />
</div>
</div>
</a-layout>
</a-layout>
</Wrapper>
</template>
<script lang="ts">
import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { message } from 'ant-design-vue';
import { getTeleport } from '@antv/x6-vue-shape';
import { Graph, Node, type NodeProperties } from '@antv/x6';
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import Header from '../header.vue';
import type { PlatformWithComponents, Scenario } from './types';
import { createGraphTaskElement, createLineOptions, type GraphContainer, 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 { saveScenario } from './api';
const TeleportContainer = defineComponent(getTeleport());
registerScenarioElement();
export default defineComponent({
components: {
PlatformCard,
NodesCard,
Wrapper,
Header,
SaveOutlined,
CheckCircleOutlined,
CheckOutlined,
RollbackOutlined,
TeleportContainer,
},
setup() {
const canvas = ref<HTMLDivElement | null>(null);
const graph = ref<Graph | null>(null);
const currentZoom = ref<number>(1);
const draggedNodeData = ref<PlatformWithComponents | null>(null);
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);
const scenariosCardRef = ref<InstanceType<typeof PlatformCard> | null>(null);
const {
handleGraphEvent,
createCanvas,
zoomIn,
zoomOut,
fitToScreen,
centerContent,
resizeCanvas,
} = useGraphCanvas();
// 处理拖动开始
const handleDragStart = (nm: PlatformWithComponents) => {
draggedNodeData.value = nm;
};
// 处理拖动结束
const handleDragEnd = () => {
isDraggingOver.value = false;
};
// 处理拖动进入
const handleDragEnter = (e: DragEvent) => {
safePreventDefault(e);
safeStopPropagation(e);
isDraggingOver.value = true;
};
// 处理拖动离开
const handleDragLeave = (e: DragEvent) => {
safePreventDefault(e);
safeStopPropagation(e);
if (canvas.value && e.relatedTarget &&
typeof e.relatedTarget === 'object' &&
'nodeType' in e.relatedTarget) {
// 使用 Element 类型而不是 x6 的 Node 类型
if (!canvas.value.contains(e.relatedTarget as Element)) {
isDraggingOver.value = false;
}
}
};
// 处理放置
const handleDrop = (e: DragEvent) => {
console.info('handleDrop', e);
safePreventDefault(e);
safeStopPropagation(e);
isDraggingOver.value = false;
currentScenarioEditing.value = false;
if (!currentScenario.value) {
message.error('请先选择场景.');
return;
}
if (!graph.value || !canvas.value || !draggedNodeData.value) {
message.error('无法放置节点,缺少必要数据');
return;
}
try {
// 获取拖动的数据
const pwc = draggedNodeData.value as PlatformWithComponents;
// if (!hasElements(graph.value as Graph)) {
// message.error('请先添加根节点.');
// return;
// }
// if (hasRootElementNode(graph.value as Graph)) {
// message.error('根节点已经存在.');
// return;
// }
// 计算相对于画布的位置(考虑缩放)
const rect = canvas.value.getBoundingClientRect();
const scale = currentZoom.value || 1;
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
console.log('放置节点:', { ...pwc, x, y });
// 创建节点数据
const settingTaskElement: GraphTaskElement = createGraphTaskElementFromScenario(pwc, { x, y });
// 创建节点
const settingTaskNode = createGraphScenarioElement(settingTaskElement);
console.info('create settingTaskNode: ', settingTaskElement, settingTaskNode);
// 将节点添加到画布
graph.value?.addNode(settingTaskNode as any);
console.log('节点已添加到画布:', settingTaskNode.id);
// 重置拖动数据
draggedNodeData.value = null;
} catch (error) {
console.error('放置节点时出错:', error);
}
};
const handleSelect = (scenario: Scenario) => {
console.info('handleSelect', 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,
};
currentScenarioEditing.value = true;
createElements();
};
const createElements = () => {
nextTick(() => {
try {
graph.value?.clearCells();
} catch (e: any) {
console.error('clear cells error, cause:', e);
}
setTimeout(() => {
if (currentScenario.value?.graph && graph.value) {
if (currentScenario.value?.graph.nodes) {
currentScenario.value?.graph.nodes.forEach(ele => {
const node = createGraphScenarioElement(ele as GraphTaskElement);
console.info('create node: ', ele);
// 将节点添加到画布
graph.value?.addNode(node as Node);
});
}
if (currentScenario.value?.graph.edges) {
// 然后添加所有边,确保包含桩点信息
setTimeout(() => {
currentScenario.value?.graph.edges.forEach(edgeData => {
graph.value?.addEdge({
...edgeData,
...createLineOptions(),
});
});
}, 100); // 延迟一会儿,免得连线错位
}
}
}, 100);
});
};
const handleCreate = () => {
currentScenario.value = {
id: 0,
name: null,
description: null,
communicationGraph: null,
graph: {
edges: [],
nodes: [],
}
};
currentGraph.value = {
edges: [],
nodes: [],
};
selectedModelNode.value = null;
selectedNodeTaskElement.value = null;
createElements();
};
// 初始化X6画布
const initGraph = () => {
if (!canvas.value) {
console.error('画布容器不存在');
return;
}
try {
graph.value = createCanvas(canvas.value);
console.log('画布初始化成功');
createElements();
// 监听缩放变化
handleGraphEvent('scale', ({ sx }: { sx: number }) => {
currentZoom.value = sx;
});
handleGraphEvent('blank:click', () => {
selectedModelNode.value = null;
selectedNodeTaskElement.value = null;
currentScenarioEditing.value = null !== currentScenario.value;
});
handleGraphEvent('node:click', (args: any) => {
const node = args.node as Node<NodeProperties>;
const newElement = node.getData() as GraphTaskElement;
selectedModelNode.value = node;
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as GraphTaskElement;
});
// 监听节点鼠标事件,显示/隐藏连接点
handleGraphEvent('node:mouseenter', (_ctx: any) => {
});
handleGraphEvent('node:mouseleave', (_ctx: any) => {
});
} catch (error) {
console.error('初始化画布失败:', error);
}
};
// 监听窗口大小变化
const handleResize = () => {
nextTick(() => {
resizeCanvas();
});
};
const init = () => {
console.info('init');
nextTick(() => {
initGraph();
window.addEventListener('resize', handleResize);
console.log('节点挂载完成');
});
};
const handleUpdateElement = (element: GraphTaskElement) => {
// 更新选中的节点数据
if (selectedModelNode.value) {
selectedModelNode.value.replaceData(element);
}
console.info('handleUpdateElement', element);
// 更新本地引用
selectedNodeTaskElement.value = element;
changed.value = true;
};
const handleSave = () => {
const graphData: GraphContainer = resolveGraph(graph.value as Graph);
console.info('handleSave', graphData);
if (!currentScenario.value) {
message.error('当前决策树不存在');
return;
}
const newScenario: Scenario = {
...currentScenario.value,
graph: graphData,
communicationGraph: JSON.stringify(graphData),
};
if (!newScenario.name) {
message.error('场景名称不能为空.');
return;
}
let res = null;
if (currentScenario.value.id > 0) {
res = saveScenario(newScenario);
} else {
res = saveScenario(newScenario);
}
res.then(r => {
if (r.code === 200) {
scenariosCardRef.value?.refresh();
message.success(r.msg ?? '操作成功.');
} else {
message.error(r.msg ?? '操作失败.');
}
});
};
// 初始化
onMounted(() => {
init();
});
// 清理
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
if (graph.value) {
try {
graph.value.clearCells();
} catch (error) {
console.warn('销毁画布时出错:', error);
}
graph.value = null;
console.log('画布已销毁');
}
});
return {
scenariosCardRef,
handleCreate,
currentScenarioEditing,
currentScenario,
currentGraph,
selectedNodeTaskElement,
selectedModelNode,
graph,
canvas,
zoomIn,
zoomOut,
fitToScreen,
centerContent,
handleDragStart,
handleDragEnd,
handleDragEnter,
handleDragLeave,
handleDrop,
isDraggingOver,
handleSave,
handleUpdateElement,
handleSelect,
};
},
});
</script>

View File

@@ -2,32 +2,51 @@
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
<a-card
:class="[
'ks-designer-node',
`ks-designer-${element?.category ?? 'model'}-node`
'ks-scenario-node',
`ks-scenario-${element?.category ?? 'model'}-node`
]"
hoverable
>
<template #title>
<a-space>
<span class="ks-designer-node-title">{{ element?.name ?? '-' }}</span>
<span class="ks-scenario-node-title">{{ element?.description ?? element?.name ?? '-' }}</span>
</a-space>
</template>
<div class="port port-in" data-port="in-0" magnet="passive"></div>
<!-- 节点内容区域 -->
<div class="w-full">
<a-tooltip v-if="(element?.description ?? (element?.name ?? '-')).length >= 38">
<template #title>
{{ element?.description ?? element?.name }}
</template>
<p>
{{ substring(element?.description ?? (element?.name ?? '-'), 40) }}
</p>
</a-tooltip>
<p v-else>
{{ substring(element?.description ?? (element?.name ?? '-'), 40) }}
</p>
<div class="ks-scenario-node-content">
<div
v-for="(item, index) in element?.components || []"
:key="item.id || index"
class="ks-scenario-node-row"
>
<div
:data-port="`in-${item.id || index}`"
:title="`入桩: ${item.name}`"
class="port port-in"
magnet="passive"
>
<div class="triangle-left"></div>
</div>
<!-- child名称 -->
<div class="ks-scenario-node-name">
{{ substring(item.description ?? item.name, 20) }}
</div>
<!-- 右侧出桩只能作为连线源 -->
<div
:data-port="`out-${item.id || index}`"
:title="`出桩: ${item.name}`"
class="port port-out"
magnet="active"
>
<div class="triangle-right" ></div>
</div>
</div>
</div>
</div>
<div class="port port-out" data-port="out-0" magnet="active"></div>
</a-card>
<template #overlay>
@@ -45,8 +64,8 @@
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import { elementProps } from './props';
import type { ModelElement } from './element';
import { elementProps, type ModelElement } from '../graph';
import { DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
import type { Graph } from '@antv/x6';
import { substring } from '@/utils/strings';
@@ -111,6 +130,7 @@ export default defineComponent({
onMounted(() => {
_props.node?.on('change:data', handleDataChange);
console.error('element',element.value)
});
onUnmounted(() => {
@@ -128,7 +148,7 @@ export default defineComponent({
</script>
<style lang="less">
.ks-designer-node {
.ks-scenario-node {
background: linear-gradient(150deg, rgba(108, 99, 255) 1%, rgba(108, 99, 255) 100%);
border-radius: 8px;
width: 100%;
@@ -147,35 +167,37 @@ export default defineComponent({
.ant-card-head {
border: 0;
height: 25px;
height: 28px;
min-height: 25px;
border-radius: 0;
color: #fff;
font-size: 12px;
font-weight: normal;
padding: 0 20px;
background: linear-gradient(to bottom, #3a4c70, #2d3a56);
//background: linear-gradient(to bottom, #3a4c70, #2d3a56);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background: linear-gradient(to bottom, rgba(108, 99, 255, 0.15), rgba(108, 99, 255, 0.05));
background: url('@/assets/icons/bg-node-head.png') center / 100% 100%;
//background: url('@/assets/icons/bg-node-head.png') center / 100% 100%;
//background: linear-gradient(to bottom, rgb(234 234 234 / 20%), rgb(191 191 191 / 58%));
background: url('@/assets/icons/card-head-red.png') center / 100% 100%;
}
.ks-designer-node-icon {
.ks-scenario-node-icon {
width: 15px;
height: 15px;
display: block;
position: absolute;
left: 8px;
top: 13px;
background: url('@/assets/icons/model-4.svg') center / 100% 100%;
top: 6px;
background: url('@/assets/icons/icon-node.svg') center / 100% 100%;
}
.ks-designer-node-title {
font-size: 13px;
.ks-scenario-node-title {
font-size: 12px;
color: #fff;
color: #312e2e;
margin-top: -7px;
display: block;
}
.ant-card-body {
@@ -183,8 +205,8 @@ export default defineComponent({
height: calc(100% - 25px);
border-radius: 0;
font-size: 12px;
padding: 8px 15px !important;
border-top: 1px solid rgba(108, 99, 255, 0.5);
padding: 10px 30px !important;
//border-top: 1px solid rgba(108, 99, 255, 0.5);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -197,79 +219,16 @@ export default defineComponent({
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
}
// model/task
//&.ks-designer-model-node,
//&.ks-designer-task-node {
// background: linear-gradient(150deg, rgba(92,84,247,0.9) 1%, rgba(115,108,250,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(92,84,247,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/m-02.png') center / 100% 100%;
// }
//}
//
//// input
//&.ks-designer-input-node {
// background: linear-gradient(150deg, rgba(82,73,245,0.9) 1%, rgba(105,98,249,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(82,73,245,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/icon-model-input.png') center / 100% 100%;
// }
//}
//
//// action action-node
//&.ks-designer-action-node {
// background: linear-gradient(150deg, rgba(108,99,255,0.9) 1%, rgba(140,133,255,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(108,99,255,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/bg-fk-point.png') center / 100% 100%;
// }
//}
//
//// precondition/component
//&.ks-designer-precondition-node,
//&.ks-designer-component-node {
// background: linear-gradient(150deg, rgba(72,64,243,0.9) 1%, rgba(95,88,248,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(72,64,243,0.5);
// }
//}
//
//// select/control
//&.ks-designer-select-node,
//&.ks-designer-control-node {
// background: linear-gradient(150deg, rgba(90,82,246,0.9) 1%, rgba(118,111,251,0.7) 55%);
//
// .ant-card-body {
// border-top: 1px solid rgba(90,82,246,0.5);
// }
//
// .ks-designer-node-icon {
// background: url('@/assets/icons/bg-model-builder-card-title.png') center / 100% 100%;
// }
//}
//
.ks-designer-node-content {
.ks-scenario-node-content {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
gap: 6px;
}
.ks-designer-node-row {
.ks-scenario-node-row {
width: 100%;
display: flex;
align-items: center;
@@ -286,24 +245,51 @@ export default defineComponent({
box-shadow: 0 0 0 2px rgb(108, 99, 255, 0.8);
z-index: 10;
magnet: true;
position: relative;
.triangle-left {
width: 0;
height: 0;
border-top: 4px solid transparent;
border-right: 5px solid #5da1df;
border-bottom: 4px solid transparent;
position: absolute;
left: -8px;
top: 0.5px;
magnet: passive;
}
/* 右三角形 */
.triangle-right {
width: 0;
height: 0;
border-top: 4px solid transparent;
border-left: 5px solid #5da1df;
border-bottom: 4px solid transparent;
position: absolute;
right: -8px;
top: 0.5px;
magnet: passive;
}
}
//
.port-in {
background-color: #6C63FF;
//background-color: #3c82f6;
margin-right: 8px;
//border: 1px solid #093866;
magnet: passive;
box-shadow: none;
width: 15px;
height: 15px;
width: 13px;
height: 13px;
display: block;
background: url('@/assets/icons/point.svg') center / 100% 100%;
//background: url('@/assets/icons/point.svg') center / 100% 100%;
border: 2px solid #5da1df;
position: absolute;
//top: 7px;
left: -8px;
top: 50%;
left: -17px;
}
.port-out {
@@ -311,26 +297,23 @@ export default defineComponent({
margin-right: 5px;
magnet: active;
box-shadow: none;
width: 15px;
height: 15px;
width: 13px;
height: 13px;
display: block;
background: url('@/assets/icons/point.svg') center / 100% 100%;
border: 2px solid #5da1df;
background:#5da1df;
position: absolute;
//right: 8px;
//top: 7px;
top: 50%;
right: -12px;
right: -17px;
}
//
.ks-designer-node-name {
.ks-scenario-node-name {
flex: 1;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
//white-space: nowrap;
}
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="w-full">
<a-collapse v-model:activeKey="activeKey" :accordion="true" class="platform-collapse">
<a-collapse-panel key="1">
<template #header>
<span class="ks-model-builder-title-icon icon-model"></span>平台名称
</template>
<div class="w-full h-full">
<a-row>
<a-col v-for="nm in templateData" :span="12">
<div
:key="nm.id"
class="ks-model-drag-item"
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
>
<img :alt="nm.description ?? nm.name ?? ''" class="icon" src="@/assets/icons/model.svg" />
<span class="desc">{{ nm.description ?? nm.name }}</span>
</div>
</a-col>
</a-row>
</div>
</a-collapse-panel>
</a-collapse>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, type PropType, ref } from 'vue';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import {findPlatformWithComponents} from './api'
import { type PlatformWithComponents, type Scenario } from './types';
export default defineComponent({
emits: ['drag-item-start', 'drag-item-end'],
props: {
scenario: {
type: Object as PropType<Scenario>,
required: true,
}
},
setup(_props, { emit }) {
const activeKey = ref<number>(1);
const templateData = ref<PlatformWithComponents[]>([]);
const isDraggingOver = ref<boolean>(false);
const draggedNodeData = ref<PlatformWithComponents | null>(null);
const currentScenario = ref<Scenario|null>(_props.scenario)
const loadTress = () => {
templateData.value = []
findPlatformWithComponents(_props.scenario?.id).then(r => {
templateData.value = r.data ?? []
});
};
const handleDragStart = (e: DragEvent, nm: PlatformWithComponents) => {
let dragNode: PlatformWithComponents = { ...nm };
draggedNodeData.value = dragNode as PlatformWithComponents;
if (e.dataTransfer) {
e.dataTransfer.setData('text/plain', JSON.stringify(draggedNodeData.value));
e.dataTransfer.effectAllowed = 'copyMove';
const dragPreview = document.createElement('div');
dragPreview.textContent = dragNode.name || '';
dragPreview.style.cssText = `
position: absolute;
top: -1000px;
padding: 6px 12px;
background: #3b82f6;
color: white;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
`;
document.body.appendChild(dragPreview);
e.dataTransfer.setDragImage(dragPreview, dragPreview.offsetWidth / 2, dragPreview.offsetHeight / 2);
emit('drag-item-start', dragNode, isDraggingOver.value, e);
console.log('开始拖动:', dragNode);
setTimeout(() => document.body.removeChild(dragPreview), 0);
}
};
const handleDragEnd = (e: DragEvent) => {
safePreventDefault(e);
safeStopPropagation(e);
isDraggingOver.value = false;
console.log('拖动结束');
emit('drag-item-end', isDraggingOver.value, e);
};
const load = ()=> {
findPlatformWithComponents(1).then(re=> {
console.error(re);
})
}
onMounted(() => {
loadTress();
load();
});
return {
activeKey,
templateData,
handleDragStart,
handleDragEnd,
};
},
});
</script>

View File

@@ -0,0 +1,142 @@
<template>
<a-collapse v-model:activeKey="activeKey" :accordion="false" class="ks-trees-collapse">
<a-collapse-panel key="1" style="position: relative">
<template #header>
<a-flex>
<span class="ks-model-builder-title-icon icon-model"></span>
<span style="color: #82c4e9;font-size: 16px;">场景</span>
<!-- <span style="position: absolute; right: 15px;"><PlusOutlined @click="$emit('create-tree')"/></span>-->
</a-flex>
</template>
<div class="w-full" style="padding: 5px;">
<a-flex>
<a-input-search v-model:value="scenarioQuery.name" allowClear placeholder="场景名称" size="small" @search="loadTress" />
<!-- <a-button size="small" style="margin-left: 10px;">-->
<!-- <PlusOutlined style="margin-top: 0px;display: block;" @click="$emit('create-tree')" />-->
<!-- </a-button>-->
</a-flex>
</div>
<a-list :data-source="scenario || []" size="small" style="min-height: 25vh">
<template #renderItem="{ item }">
<a-list-item>
<a-flex>
<a-tooltip placement="bottom">
<template #title>
<p>名称: {{ item.name }}</p>
<p>说明: {{ item.description }}</p>
</template>
<span>{{ substring(item.name, 15) }}</span>
</a-tooltip>
<a-flex class="ks-tree-actions">
<span style="margin-right: 10px" @click="()=> handleSelect(item)"><EditFilled /></span>
<!-- <a-popconfirm-->
<!-- title="确定删除?"-->
<!-- @confirm="()=> handleDelete(item)"-->
<!-- >-->
<!-- <span class="ks-tree-action ks-tree-action-delete"><DeleteOutlined /></span>-->
<!-- </a-popconfirm>-->
</a-flex>
</a-flex>
</a-list-item>
</template>
</a-list>
<a-pagination
v-if="totalTress > Number(scenarioQuery?.pageSize ?? 0)"
v-model:current="scenarioQuery.pageNum"
:page-size="scenarioQuery.pageSize"
:total="totalTress"
simple size="small" @change="handleChange" />
</a-collapse-panel>
</a-collapse>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { CheckOutlined, DeleteOutlined, EditFilled, PlusOutlined } from '@ant-design/icons-vue';
import type { Scenario, ScenarioRequest } from './types';
import { deleteOneScenarioById, findScenarioByQuery } from './api';
import { substring } from '@/utils/strings';
export default defineComponent({
emits: ['select', 'create'],
components: {
CheckOutlined,
PlusOutlined,
DeleteOutlined,
EditFilled,
},
setup(_props, { emit }) {
const scenario = ref<Scenario[]>([]);
const scenarioQuery = ref<Partial<ScenarioRequest>>({
name: null,
pageNum: 1,
pageSize: 8,
});
const activeKey = ref<number>(1);
const totalTress = ref<number>(0);
const loadTress = () => {
findScenarioByQuery(scenarioQuery.value).then(r => {
scenario.value = r.rows;
totalTress.value = r.total ?? 0;
});
};
const handleChange = (page: number, pageSize: number) => {
scenarioQuery.value.pageNum = page;
scenarioQuery.value.pageSize = pageSize;
loadTress();
};
const handleDelete = (item: Scenario) => {
deleteOneScenarioById(item.id).then(r => {
if (r.code === 200) {
loadTress();
}
});
};
const columns = [
{
title: '名称',
dataIndex: 'name',
},
];
const handleSelect = (record: Scenario) => {
emit('select', record);
};
const customRow = (record: Scenario) => {
return {
onClick: (event: any) => {
emit('select', record, event);
},
};
};
const refresh = () => loadTress();
onMounted(() => {
loadTress();
});
return {
refresh,
totalTress,
substring,
activeKey,
scenario,
scenarioQuery,
loadTress,
columns,
customRow,
handleSelect,
handleChange,
handleDelete,
};
},
});
</script>

View File

@@ -18,10 +18,10 @@
import { register } from '@antv/x6-vue-shape';
import ModelElement from './node.vue';
export const registerNodeElement = () => {
console.info('registerNodeElement');
export const registerScenarioElement = () => {
console.info('registerScenarioElement');
register({
shape: 'task',
shape: 'scenario',
component: ModelElement,
width: 120,
attrs: {

View File

@@ -0,0 +1,53 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { ApiDataResponse, NullableString, PageableResponse } from '@/types';
import type { GraphContainer } from '../graph';
export interface Scenario {
id: number,
name: NullableString,
description: NullableString,
// 用于存储场景中的通讯关系
communicationGraph: NullableString,
graph: GraphContainer
}
export interface ScenarioRequest extends Scenario {
pageNum: number,
pageSize: number,
}
export interface ScenarioPageableResponse extends PageableResponse<Scenario> {
}
export interface Platform {
id: number,
name: NullableString,
description: NullableString,
scenarioId: number,
}
export interface PlatformComponent {
id: number,
name: NullableString,
type: NullableString,
description: NullableString,
platformId: number,
}
export interface PlatformWithComponents extends Platform {
components: PlatformComponent[],
}
export interface PlatformWithComponentsResponse extends ApiDataResponse<PlatformWithComponents[]> {
}

View File

@@ -0,0 +1,79 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { GraphRect, GraphTaskElement } from '../graph';
import { generateKey } from '@/utils/strings';
import type { PlatformWithComponents } from './types';
export const createGraphTaskElementFromScenario = (
platform: PlatformWithComponents,
rect?: GraphRect,
): GraphTaskElement => {
let realRect = { width: 120, height: 80, x: 0, y: 0, ...rect || {} };
console.info('rect', rect);
return {
id: 0,
key: generateKey(),
type: 'scenario',
name: platform.name,
platformId: platform.id,
scenarioId: platform.scenarioId,
components: platform.components ?? [],
template: 0,
templateType: null,
category: null,
group: null,
description: platform.description,
order: 0,
position: {
x: realRect.x ?? 0,
y: realRect.y ?? 0,
},
width: realRect.width,
height: realRect.height,
inputs: null,
outputs: null,
parameters: [],
variables: [],
} as GraphTaskElement;
};
export const createGraphScenarioElement = (element: GraphTaskElement): any => {
let realHeight = 120;
let width: number = 250;
if (!realHeight) {
realHeight = 120;
}
if(element?.components){
if(element?.components?.length > 2){
realHeight = 90 + element?.components?.length * 25
} else if(element?.components?.length <=1){
realHeight = 120
}
}
return {
shape: 'scenario',
id: element.key,
position: {
x: element.position?.x || 0,
y: element.position?.y || 0,
},
size: {
width: width,
height: realHeight,
},
attrs: {
label: {
text: element.name,
},
},
data: element,
};
};

View File

@@ -8,7 +8,8 @@
*/
import { HttpRequestClient } from '@/utils/request';
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, BehaviorTreeRequest, NodeTemplatesResponse } from './types';
import type { NodeTemplatesResponse } from './template';
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, BehaviorTreeRequest } from './tree';
import type { BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({

View File

@@ -19,27 +19,12 @@
<div class="ks-model-builder-content">
<div class="ks-model-builder-actions">
<a-space>
<!-- <a-tooltip v-if="graph && currentBehaviorTree" placement="top">-->
<!-- <template #title>-->
<!-- 保存-->
<!-- </template>-->
<!-- <a-popconfirm-->
<!-- title="确定保存?"-->
<!-- @confirm="handleSave"-->
<!-- >-->
<!-- <a-button class="ks-model-builder-save" size="small">-->
<!-- <CheckOutlined />-->
<!-- <span>保存</span>-->
<!-- </a-button>-->
<!-- </a-popconfirm>-->
<!-- </a-tooltip>-->
<a-button v-if="graph && currentBehaviorTree" class="ks-model-builder-save" size="small" @click="handleSave">
<CheckOutlined />
<span>保存</span>
</a-button>
</a-space>
</div>
<!-- 画布容器添加拖放事件 -->
<div
ref="canvas"
class="ks-model-builder-canvas"
@@ -72,16 +57,16 @@ import { Graph, Node, type NodeProperties } from '@antv/x6';
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
import Header from './header.vue';
import Header from '../header.vue';
import Properties from './properties.vue';
import type { BehaviorTree, NodeTemplate } from './types';
import type { GraphTaskElement, NodeGraph } from './builder/element';
import { useGraphCanvas } from './builder/hooks';
import { registerNodeElement } from './builder/register';
import { createLineOptions } from './builder/line';
import type { NodeDragTemplate } from './template';
import type { BehaviorTree } from './tree';
import { createGraphTaskElementFromTemplate } from './utils';
import { createGraphTaskElement, createLineOptions, type GraphContainer, type GraphTaskElement, hasElements, hasRootElementNode, resolveGraph, useGraphCanvas } from '../graph';
import { registerNodeElement } from './register';
import { createTree, findOneTreeById, updateTree } from './api';
import { createGraphTaskElement, hasElements, hasRootElementNode, resolveNodeGraph } from './builder/utils';
import { createGraphTaskElementFromTemplate } from './utils/node';
import TressCard from './trees-card.vue';
import NodesCard from './nodes-card.vue';
@@ -106,11 +91,11 @@ export default defineComponent({
const canvas = ref<HTMLDivElement | null>(null);
const graph = ref<Graph | null>(null);
const currentZoom = ref<number>(1);
const draggedNodeData = ref<NodeTemplate | null>(null);
const draggedNodeData = ref<NodeDragTemplate | null>(null);
const isDraggingOver = ref(false);
const currentTreeEditing = ref<boolean>(false);
const currentBehaviorTree = ref<BehaviorTree | null>(null);
const currentNodeGraph = ref<NodeGraph | null>(null);
const currentGraph = ref<GraphContainer | null>(null);
const selectedModelNode = ref<Node<NodeProperties> | null>(null);
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
const changed = ref<boolean>(false);
@@ -127,7 +112,7 @@ export default defineComponent({
} = useGraphCanvas();
//
const handleDragStart = (nm: NodeTemplate) => {
const handleDragStart = (nm: NodeDragTemplate) => {
draggedNodeData.value = nm;
};
@@ -178,7 +163,7 @@ export default defineComponent({
try {
//
const template = draggedNodeData.value as NodeTemplate;
const template = draggedNodeData.value as NodeDragTemplate;
if (!hasElements(graph.value as Graph) && template.type !== 'root') {
message.error('请先添加根节点.');
@@ -219,9 +204,9 @@ export default defineComponent({
console.info('handleSelectTree', tree);
findOneTreeById(tree.id).then(r => {
if (r.data) {
let nodeGraph: NodeGraph | null = null;
let nodeGraph: GraphContainer | null = null;
try {
nodeGraph = JSON.parse(r.data?.xmlContent as unknown as string) as unknown as NodeGraph;
nodeGraph = JSON.parse(r.data?.xmlContent as unknown as string) as unknown as GraphContainer;
} catch (e: any) {
console.error('parse error,cause:', e);
}
@@ -290,7 +275,7 @@ export default defineComponent({
},
updatedAt: null,
};
currentNodeGraph.value = {
currentGraph.value = {
edges: [],
nodes: [],
};
@@ -370,7 +355,7 @@ export default defineComponent({
};
const handleSave = () => {
const graphData: NodeGraph = resolveNodeGraph(graph.value as Graph);
const graphData: GraphContainer = resolveGraph(graph.value as Graph);
console.info('handleSave', graphData);
if (!currentBehaviorTree.value) {
message.error('当前决策树不存在');
@@ -429,7 +414,7 @@ export default defineComponent({
handleCreateTree,
currentTreeEditing,
currentBehaviorTree,
currentNodeGraph,
currentGraph,
selectedNodeTaskElement,
selectedModelNode,
graph,

View File

@@ -0,0 +1,428 @@
<template>
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
<a-card
:class="[
'ks-designer-node',
`ks-designer-${element?.category ?? 'model'}-node`,
`ks-designer-group-${element?.group ?? 'general'}`
]"
hoverable
>
<template #title>
<a-space>
<!-- <span class="ks-designer-node-icon"></span>-->
<span class="ks-designer-node-title">{{ element?.name ?? '-' }}</span>
</a-space>
</template>
<div class="port port-in" data-port="in-0" magnet="passive">
<div class="triangle-left"></div>
</div>
<div class="w-full ks-designer-node-text">
<a-tooltip >
<template #title>
{{ element?.description ?? element?.name }}
</template>
<p class="ks-designer-node-label">
{{ substring(element?.name ?? (element?.name ?? '-'), 40) }}
</p>
</a-tooltip>
</div>
<div class="port port-out" data-port="out-0" magnet="active">
<div class="triangle-right" ></div>
</div>
</a-card>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item key="delete">
<template #icon>
<DeleteOutlined />
</template>
删除
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</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 type { Graph } from '@antv/x6';
import { substring } from '@/utils/strings';
export default defineComponent({
name: 'ModelElement',
components: {
SettingOutlined,
DeleteOutlined,
},
props: elementProps,
setup(_props) {
const element = ref<ModelElement | null>(
_props.node ? (_props.node.getData() as ModelElement) : null,
);
const updateKey = ref(0);
const isMenuVisible = ref(false);
// 获取画布实例
const getGraph = (): Graph | null => {
return _props.graph as Graph || null;
};
// 监听节点数据变化
const handleDataChange = () => {
if (_props.node) {
element.value = _props.node.getData() as ModelElement;
} else {
element.value = null;
}
updateKey.value++;
};
const handleVisibleChange = (visible: boolean) => {
isMenuVisible.value = visible;
};
const handleMenuClick = ({ key }: { key: string }) => {
if (key === 'delete') {
handleDelete();
}
};
const handleDelete = () => {
if (!_props.node) return;
const graph = getGraph();
if (graph) {
try {
// 先删除关联边
const connectedEdges = graph.getConnectedEdges(_props.node);
connectedEdges.forEach(edge => graph.removeEdge(edge));
// 再删除节点
graph.removeNode(_props.node);
console.info(`节点 ${_props.node.id} 已删除`);
} catch (error) {
console.error('删除节点失败:', error);
}
}
isMenuVisible.value = false;
};
onMounted(() => {
_props.node?.on('change:data', handleDataChange);
});
onUnmounted(() => {
_props.node?.off('change:data', handleDataChange);
});
return {
element,
substring,
handleMenuClick,
handleVisibleChange,
};
},
});
</script>
<style lang="less">
.ks-designer-node {
background: linear-gradient(150deg, rgba(108, 99, 255) 1%, rgba(108, 99, 255) 100%);
border-radius: 8px;
width: 100%;
height: 100%;
cursor: pointer;
position: relative;
background: #1e2533;
border: 1px solid #4a7aff;
border: 2px solid #000000;
&:hover {
border: 2px solid #4a7aff;
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
}
.ant-card-head {
border: 0;
height: 28px;
min-height: 25px;
border-radius: 0;
color: #fff;
font-size: 12px;
font-weight: normal;
padding: 0 20px;
//background: linear-gradient(to bottom, #3a4c70, #2d3a56);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background: linear-gradient(to bottom, rgba(108, 99, 255, 0.15), rgba(108, 99, 255, 0.05));
//background: url('@/assets/icons/bg-node-head.png') center / 100% 100%;
//background: linear-gradient(to bottom, rgb(234 234 234 / 20%), rgb(191 191 191 / 58%));
}
.ks-designer-node-icon {
width: 15px;
height: 15px;
display: block;
position: absolute;
left: 8px;
top: 6px;
background: url('@/assets/icons/icon-node.svg') center / 100% 100%;
}
.ks-designer-node-title {
font-size: 12px;
color: #fff;
margin-top: -7px;
display: block;
}
.ant-card-body {
color: #f5f5f5;
height: calc(100% - 25px);
border-radius: 0;
font-size: 12px;
padding: 10px 30px !important;
//border-top: 1px solid rgba(108, 99, 255, 0.5);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
white-space: normal; // 恢复默认的换行行为
word-wrap: break-word; // 允许长单词换行
word-break: break-all; // 允许在任意字符处换行
line-height: 1.4; // 增加行高提升可读性
box-shadow: 0 0 10px rgba(74, 122, 255, 0.3);
}
// 连接桩容器样式
.ks-designer-node-content {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
}
.ks-designer-node-row {
width: 100%;
display: flex;
align-items: center;
position: relative;
min-height: 24px;
}
.port {
width: 12px;
height: 12px;
border-radius: 50%;
cursor: crosshair;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgb(108, 99, 255, 0.8);
z-index: 10;
magnet: true;
position: relative;
.triangle-left {
width: 0;
height: 0;
border-top: 4px solid transparent;
border-right: 5px solid #5da1df;
border-bottom: 4px solid transparent;
position: absolute;
left: -8px;
top: 0.5px;
magnet: passive;
}
/* 右三角形 */
.triangle-right {
width: 0;
height: 0;
border-top: 4px solid transparent;
border-left: 5px solid #5da1df;
border-bottom: 4px solid transparent;
position: absolute;
right: -8px;
top: 0.5px;
magnet: passive;
}
}
// 左侧入桩样式
.port-in {
//background-color: #3c82f6;
margin-right: 8px;
//border: 1px solid #093866;
magnet: passive;
box-shadow: none;
width: 13px;
height: 13px;
display: block;
//background: url('@/assets/icons/point.svg') center / 100% 100%;
border: 2px solid #5da1df;
position: absolute;
//top: 7px;
left: 10px;
top: 50%;
}
.port-out {
margin-left: 8px;
margin-right: 5px;
magnet: active;
box-shadow: none;
width: 13px;
height: 13px;
display: block;
//background: url('@/assets/icons/point.svg') center / 100% 100%;
border: 2px solid #5da1df;
background:#5da1df;
position: absolute;
//right: 8px;
//top: 7px;
top: 50%;
right: 6px;
}
// 节点文本样式
.ks-designer-node-name {
flex: 1;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
//white-space: nowrap;
}
&.ks-designer-root-node{
.ks-designer-node-icon {
background: url('@/assets/icons/icon-root.svg') center / 100% 100%;
}
}
&.ks-designer-action-node{
.ant-card-head {
background: url('@/assets/icons/card-head-red.png') center / 100% 100%;
}
.ks-designer-node-icon {
background: url('@/assets/icons/icon-action.svg') center / 100% 100%;
}
}
&.ks-designer-sequence-node{
.ks-designer-node-icon {
background: url('@/assets/icons/icon-sequence.svg') center / 100% 100%;
}
}
&.ks-designer-parallel-node{
.ks-designer-node-icon {
background: url('@/assets/icons/icon-parallel.svg') center / 100% 100%;
}
}
&.ks-designer-precondition-node{
.ks-designer-node-icon {
background: url('@/assets/icons/icon-branch.svg') center / 100% 100%;
}
}
&.ks-designer-group-control,
&.ks-designer-group-condition {
.ant-card-head{
display:none;
}
.ant-card-body {
height: calc(100%);
border-radius: 8px;
background: url('@/assets/icons/card-head-gray.png') center / 100% 100%;
}
&.ks-designer-root-node{
.ant-card-body {
background: url('@/assets/icons/card-head-dark.png') center / 100% 100%;
}
}
&.ks-designer-sequence-node{
.ant-card-body {
background: url('@/assets/icons/card-head-green.png') center / 100% 100%;
}
}
&.ks-designer-parallel-node{
.ant-card-body {
background: url('@/assets/icons/card-head-blue.png') center / 100% 100%;
}
}
&.ks-designer-precondition-node{
.ant-card-body {
background: url('@/assets/icons/card-head-dark.png') center / 100% 100%;
}
}
.port-in,
.port-out {
top: 40%;
}
.ks-designer-node-text{
line-height: 38px;
}
}
//&.ks-designer-precondition-node{
// border:0;
// box-shadown:none;
// &:hover{
// border:0;
// box-shadown:none;
// }
// background: url('@/assets/icons/lx.svg') center / 100% 100%;
// //transform: rotate(45deg);
// .ant-card-body {
// border: 0;
// box-shadow: none;
// height: 95px;
// line-height: 80px;
// font-size: 10px;
// padding: 0 !important;
// }
// .ant-card-head {
// display: none;
// }
//
// .ks-designer-node-label {
// width: 40px; /* 保留原有宽度 */
// text-align: center; /* 保留文字居中 */
// /* 核心修改:取消固定行高,重置换行相关属性 */
// word-wrap: break-word;/* 强制换行(兼容老旧浏览器) */
// word-break: break-all;/* 截断长单词/字符确保在40px内换行 */
// white-space: normal; /* 恢复默认换行规则(避免文字不换行) */
// /* 可选:添加行间距,提升多行可读性 */
// line-height: 1.4; /* 多行时的行间距,可根据需求调整 */
// padding: 10px 0; /* 上下内边距替代原有line-height:98px的垂直居中效果 */
// margin: 31% auto;
// }
//
// .port-in {
// left: 12px;
// top: 42px;
// }
//
// .port-out {
// right: 6px;
// top: 42px;
// }
//}
}
</style>

View File

@@ -13,7 +13,7 @@
:data-type="nm.type"
class="ks-model-drag-item"
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
@dragstart="handleDragStart($event, nm, 'control')"
>
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
<span class="desc">{{ nm.name }}</span>
@@ -34,7 +34,7 @@
:data-type="nm.type"
class="ks-model-drag-item"
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
@dragstart="handleDragStart($event, nm, 'condition')"
>
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
<span class="desc">{{ nm.name }}</span>
@@ -55,7 +55,7 @@
:data-type="nm.type"
class="ks-model-drag-item"
@dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)"
@dragstart="handleDragStart($event, nm, 'action')"
>
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" />
<span class="desc">{{ nm.name }}</span>
@@ -71,7 +71,7 @@
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import type { NodeTemplate } from './types';
import type { NodeDragTemplate, NodeTemplate } from './template';
import { findNodeTemplates } from './api';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
@@ -82,7 +82,7 @@ export default defineComponent({
const activeKey = ref<number>(1);
const templateData = ref<NodeTemplate[]>([]);
const isDraggingOver = ref<boolean>(false);
const draggedNodeData = ref<NodeTemplate | null>(null);
const draggedNodeData = ref<NodeDragTemplate | null>(null);
//
const controlTemplates = ref<NodeTemplate[]>([]);
//
@@ -101,25 +101,26 @@ export default defineComponent({
r.data.forEach(tpl => {
if (tpl.type === 'action') {
actionsTemplates.value.push(tpl);
} else if (tpl.type === 'parallel' || tpl.type === 'sequence' || tpl.type === 'precondition') {
conditionTemplates.value.push(tpl);
} else {
} else if (tpl.type === 'parallel' || tpl.type === 'sequence' || tpl.type === 'select'|| tpl.type === 'root') {
controlTemplates.value.push(tpl);
} else {
conditionTemplates.value.push(tpl);
}
});
}
});
};
const handleDragStart = (e: DragEvent, nm: NodeTemplate) => {
draggedNodeData.value = nm;
const handleDragStart = (e: DragEvent, nm: NodeTemplate, group: String) => {
let dragNode: NodeDragTemplate = { ...nm, group: group };
draggedNodeData.value = dragNode as NodeDragTemplate;
if (e.dataTransfer) {
e.dataTransfer.setData('text/plain', JSON.stringify(draggedNodeData.value));
e.dataTransfer.effectAllowed = 'copyMove';
const dragPreview = document.createElement('div');
dragPreview.textContent = draggedNodeData.value.name || '';
dragPreview.textContent = dragNode.name || '';
dragPreview.style.cssText = `
position: absolute;
top: -1000px;
@@ -132,8 +133,8 @@ export default defineComponent({
`;
document.body.appendChild(dragPreview);
e.dataTransfer.setDragImage(dragPreview, dragPreview.offsetWidth / 2, dragPreview.offsetHeight / 2);
emit('drag-item-start', nm, isDraggingOver.value, e);
console.log('开始拖动:', nm);
emit('drag-item-start', dragNode, group, isDraggingOver.value, e);
console.log('开始拖动:', dragNode);
setTimeout(() => document.body.removeChild(dragPreview), 0);
}
};

View File

@@ -44,6 +44,10 @@
<a-textarea v-model:value="currentElement.description" :placeholder="currentElement.description" size="small" />
</a-form-item>
<a-form-item label="排序">
<a-input-number style="width:100%;" v-model:value="currentElement.order" size="small" />
</a-form-item>
<a-divider />
<a-form-item label="输入">
@@ -54,12 +58,12 @@
<a-textarea v-model:value="currentElement.outputs" size="small" />
</a-form-item>
<a-divider v-if="currentElement.settings && currentElement.parameters.length > 0" />
<!-- <a-divider v-if="currentElement.settings && currentElement.parameters.length > 0" />-->
<a-form-item v-for="setting in currentElement.parameters" :label="setting.description">
<a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" style="width:100%;" />
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
</a-form-item>
<!-- <a-form-item v-for="setting in currentElement.parameters" :label="setting.description">-->
<!-- <a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" style="width:100%;" />-->
<!-- <a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />-->
<!-- </a-form-item>-->
</a-form>
</a-tab-pane>
@@ -70,41 +74,56 @@
<span class="ks-model-builder-title-icon icon-input"></span>
</template>
<a-tab-pane key="1" tab="节点变量">
<div class="w-full">
<a-space>
<a-button size="small" type="primary" @click="addVariable">添加</a-button>
</a-space>
<a-table
:columns="actionSpaceColumns"
:dataSource="currentElement.variables"
:pagination="false"
:scroll="{ x: 500 }"
class="mt-1"
row-key="key"
size="small"
style="overflow-y:auto;height:35vh;"
>
<template #bodyCell="{column, record, index}">
<template v-if="column.dataIndex === 'index'">
{{ index + 1 }}
</template>
<template v-else-if="column.dataIndex === '_actions'">
<a-button
class="btn-link-delete"
danger
size="small"
type="text"
@click="()=> removeVariable(record)"
>
删除
</a-button>
</template>
<template v-else>
<a-input v-model:value="record[column.dataIndex]" size="small" />
</template>
</template>
</a-table>
</div>
<a-form
v-if="currentElement.parameters && currentElement.parameters.length > 0"
autocomplete="off"
layout="vertical"
name="basic"
style="padding-bottom:15px;"
>
<a-form-item v-for="setting in currentElement.parameters" :label="setting.description">
<a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" style="width:100%;" />
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
</a-form-item>
</a-form>
<a-empty v-else>
</a-empty>
<!-- <div class="w-full">-->
<!-- <a-space>-->
<!-- <a-button size="small" type="primary" @click="addVariable">添加</a-button>-->
<!-- </a-space>-->
<!-- <a-table-->
<!-- :columns="actionSpaceColumns"-->
<!-- :dataSource="currentElement.variables"-->
<!-- :pagination="false"-->
<!-- :scroll="{ x: 500 }"-->
<!-- class="mt-1"-->
<!-- row-key="key"-->
<!-- size="small"-->
<!-- style="overflow-y:auto;height:35vh;"-->
<!-- >-->
<!-- <template #bodyCell="{column, record, index}">-->
<!-- <template v-if="column.dataIndex === 'index'">-->
<!-- {{ index + 1 }}-->
<!-- </template>-->
<!-- <template v-else-if="column.dataIndex === '_actions'">-->
<!-- <a-button-->
<!-- class="btn-link-delete"-->
<!-- danger-->
<!-- size="small"-->
<!-- type="text"-->
<!-- @click="()=> removeVariable(record)"-->
<!-- >-->
<!-- 删除-->
<!-- </a-button>-->
<!-- </template>-->
<!-- <template v-else>-->
<!-- <a-input v-model:value="record[column.dataIndex]" size="small" />-->
<!-- </template>-->
<!-- </template>-->
<!-- </a-table>-->
<!-- </div>-->
</a-tab-pane>
</a-tabs>
@@ -128,8 +147,8 @@
<script lang="ts">
import { defineComponent, onMounted, type PropType, ref, watch } from 'vue';
import { CheckOutlined } from '@ant-design/icons-vue';
import type { ElementVariable, GraphTaskElement } from './builder/element';
import type { BehaviorTree } from './types';
import type { ElementVariable, GraphTaskElement } from '../graph';
import type { BehaviorTree } from './tree';
import type { Graph, Node, NodeProperties } from '@antv/x6';
import { generateKey } from '@/utils/strings';

View File

@@ -0,0 +1,41 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2025 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import { register } from '@antv/x6-vue-shape';
import ModelElement from './node.vue';
export const registerNodeElement = () => {
console.info('registerNodeElement');
register({
shape: 'task',
component: ModelElement,
width: 120,
attrs: {
body: {
stroke: 'transparent',
strokeWidth: 0,
fill: 'transparent',
rx: 4,
ry: 4,
},
},
dragging: {
enabled: true,
},
// 配置端口识别规则,
portMarkup: [
{
tagName: 'div',
selector: 'port-body',
},
],
// 告诉 X6 如何识别 Vue 组件内的端口
portAttribute: 'data-port',
});
};

View File

@@ -8,7 +8,7 @@
*/
import type { ApiDataResponse, NullableString } from '@/types';
import type { ElementParameter } from '../builder/element';
import type { ElementParameter } from '../graph';
export interface NodeTemplate {
id: number;
@@ -21,6 +21,10 @@ export interface NodeTemplate {
parameters: ElementParameter[],
}
export interface NodeDragTemplate extends NodeTemplate {
group: String
}
export interface NodeTemplatesResponse extends ApiDataResponse<NodeTemplate[]> {
}

View File

@@ -8,7 +8,7 @@
*/
import type { ApiDataResponse, NullableString, PageableResponse } from '@/types';
import type { NodeGraph } from '../builder/element';
import type { GraphContainer } from '../graph';
export interface BehaviorTree {
id: number,
@@ -18,7 +18,7 @@ export interface BehaviorTree {
updatedAt: NullableString,
englishName: NullableString,
xmlContent: NullableString,
graph: NodeGraph
graph: GraphContainer
}
export interface BehaviorTreeRequest extends BehaviorTree {

View File

@@ -52,7 +52,7 @@
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { CheckOutlined, DeleteOutlined, EditFilled, PlusOutlined } from '@ant-design/icons-vue';
import type { BehaviorTree, BehaviorTreeRequest } from './types';
import type { BehaviorTree, BehaviorTreeRequest } from './tree';
import { deleteOneTreeById, findTreesByQuery } from './api';
import { substring } from '@/utils/strings';
@@ -139,54 +139,3 @@ export default defineComponent({
</script>
<style lang="less">
.ant-collapse {
.ant-list-sm {
.ant-list-item {
padding: 4px 15px;
cursor: pointer;
color: rgb(130 196 233);
position: relative;
.ks-tree-actions {
position: absolute;
right: 10px;
display: none;
}
&:hover {
background: #0d2d4e;
.ks-tree-actions {
display: block;
}
}
}
}
&.ks-trees-collapse {
.ant-collapse-content-box {
padding: 0;
height: 40vh;
position: relative;
}
}
}
.create-tree-icon {
cursor: pointer;
}
.ant-list-item {
padding: 3px 5px;
cursor: pointer;
color: rgb(130 196 233);
&:hover {
background: #0d2d4e;
}
}
</style>

View File

@@ -7,13 +7,13 @@
* that was distributed with this source code.
*/
import type { NodeTemplate } from '../types';
import type { GraphTaskElement, GraphTaskRect } from '../builder/element';
import type { NodeDragTemplate } from './template';
import type { GraphRect, GraphTaskElement } from '../graph';
import { generateKey } from '@/utils/strings';
export const createGraphTaskElementFromTemplate = (
template: NodeTemplate,
rect?: GraphTaskRect,
template: NodeDragTemplate,
rect?: GraphRect,
): GraphTaskElement => {
let realRect = { width: 120, height: 80, x: 0, y: 0, ...rect || {} };
console.info('rect', rect);
@@ -25,7 +25,9 @@ export const createGraphTaskElementFromTemplate = (
templateType: template.templateType,
name: template.name,
category: template.type,
group: template.group,
description: template.description,
order: 0,
position: {
x: realRect.x ?? 0,
y: realRect.y ?? 0,

View File

@@ -8,9 +8,8 @@
*/
import { Edge, Graph, Path, Selection } from '@antv/x6';
import type { ModelElement } from './element';
import { createLineOptions, type ModelElement } from '../graph';
import type { Connecting } from '@antv/x6/lib/graph/options';
import { createLineOptions } from './line';
Graph.registerConnector(
'sequenceFlowConnector',
@@ -52,7 +51,15 @@ export const createGraphConnectingAttributes = (): Partial<Connecting> => {
const edge: Edge = this.createEdge({
shape: 'edge',
...lineOptions, // 应用动画配置
attrs: lineOptions.attrs,
attrs: {
...lineOptions.attrs,
// 在创建边时覆盖箭头配置,确保移除箭头
line: {
...lineOptions.attrs?.line,
targetMarker: null,
sourceMarker: null,
}
},
animation: lineOptions.animation,
markup: lineOptions.markup,
});
@@ -71,7 +78,7 @@ export const createGraphConnectingAttributes = (): Partial<Connecting> => {
const targetData = targetCell.getData() as ModelElement;
// 根节点不能作为子节点
if (targetData.type === 'startEvent') {
if (targetData.category === 'root') {
return false;
}

View File

@@ -7,22 +7,57 @@
* that was distributed with this source code.
*/
import type { NullableString } from '@/types';
export interface DraggableElement {
export interface GraphComponentElement {
id: number,
name: NullableString,
type: NullableString,
description: NullableString,
}
export interface GraphPosition {
x: number;
y: number;
}
export interface GraphRect {
width?: number;
height?: number;
x?: number;
y?: number;
}
export interface GraphDraggableElement {
id: number | null,
key?: NullableString,
name: NullableString,
description: NullableString,
category: NullableString,
draggable: boolean,
parent?: DraggableElement,
children: DraggableElement[]
parent?: GraphDraggableElement,
children: GraphDraggableElement[]
[key: string]: unknown;
}
export interface GraphBaseElement {
id: number;
key: NullableString;
name: NullableString;
description: NullableString;
type: NullableString;
width: number;
height: number;
position: GraphPosition;
category: NullableString;
element?: GraphDraggableElement;
components?: GraphComponentElement[]
[key: string]: unknown;
}
export type ElementStatus = 'default' | 'success' | 'failed' | 'running' | string | null
export interface ElementParameter {
id: number,
@@ -34,10 +69,6 @@ export interface ElementParameter {
templateType: NullableString,
}
export interface GraphPosition {
x: number;
y: number;
}
export interface ElementVariable {
key: NullableString;
@@ -47,33 +78,12 @@ export interface ElementVariable {
unit: NullableString;
}
export interface GraphTaskRect {
width?: number;
height?: number;
x?: number;
y?: number;
}
export interface BaseElement {
id: number;
key: NullableString;
name: NullableString;
description: NullableString;
type: NullableString;
width: number;
height: number;
position: GraphPosition;
category: NullableString;
element?: DraggableElement;
[key: string]: unknown;
}
export interface GraphTaskElement extends BaseElement {
export interface GraphTaskElement extends GraphBaseElement {
template: number;
templateType: NullableString,
inputs: any;
outputs: any;
order: number;
variables: ElementVariable[];
parameters: ElementParameter[];
children?: GraphTaskElement[],
@@ -81,7 +91,7 @@ export interface GraphTaskElement extends BaseElement {
[key: string]: unknown;
}
export interface ModelElement extends BaseElement {
export interface ModelElement extends GraphBaseElement {
edges: GraphEdgeElement[];
}
@@ -97,7 +107,7 @@ export interface GraphEdgeElement {
[key: string]: unknown;
}
export interface NodeGraph {
export interface GraphContainer {
edges: GraphEdgeElement[];
nodes: GraphTaskElement[];
}

View File

@@ -10,7 +10,7 @@
import { computed, type ComputedRef, ref, type Ref } from 'vue';
import { type Dom, Graph, Node } from '@antv/x6';
import type { NodeViewPositionEventArgs } from '@antv/x6/es/view/node/type';
import { createGraphCanvas } from './graph';
import { createGraphCanvas } from './canvas';
import { EventListener } from '@/utils/event';
import type { ModelElement } from './element';

View File

@@ -0,0 +1,16 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
export * from './line'
export * from './ports'
export * from './element'
export * from './canvas'
export * from './hooks'
export * from './props'
export * from './utils'

View File

@@ -28,6 +28,10 @@ export const createLineOptions = (): any => {
strokeWidth: 2,
strokeDasharray: ' ',
strokeDashoffset: 0,
// 去掉箭头
targetMarker: null,
sourceMarker: null,
},
marker: {
fill: '#5da0df',

View File

@@ -6,20 +6,23 @@
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { GraphEdgeElement, GraphTaskElement, NodeGraph } from './element';
import type { GraphContainer, GraphEdgeElement, GraphTaskElement } from '../graph';
import { Edge, Graph, Node } from '@antv/x6';
export const defaultHeight: Record<string, number> = {
component: 110,
};
export const createGraphTaskElement = (element: GraphTaskElement, width: number = 250, height: number = 120): any => {
export const createGraphTaskElement = (element: GraphTaskElement, width: number = 250, height: number = 120, shape: string = 'task'): any => {
let realHeight = defaultHeight[element.category as string];
if (!realHeight) {
realHeight = 120;
}
if(element.group === 'condition' || element.group === 'control') {
realHeight = 60;
}
return {
shape: 'task',
shape: shape ?? 'task',
id: element.key,
position: {
x: element.position?.x || 0,
@@ -83,7 +86,7 @@ export const resolveGraphEdgeElements = (graph: Graph): GraphEdgeElement[] => {
return edgeElements;
};
export const resolveNodeGraph = (graph: Graph): NodeGraph => {
export const resolveGraph = (graph: Graph): GraphContainer => {
const nodes: GraphTaskElement[] = resolveGraphTaskElements(graph);
const edges: GraphEdgeElement[] = resolveGraphEdgeElements(graph);
return {

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import { HttpRequestClient } from '@/utils/request';
import type { FireRule, FireRulePageableResponse, FireRuleRequest } from './types';
import type { BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api',
});
export const findFireRuleByQuery = (query: Partial<FireRuleRequest> = {}): Promise<FireRulePageableResponse> => {
return req.get('/system/rule/list', query);
};
export const createFireRule = (fireRule: FireRule): Promise<BasicResponse> => {
return req.postJson('/system/rule', fireRule);
};
export const updateFireRule = (fireRule: FireRule): Promise<BasicResponse> => {
return req.putJson('/system/rule', fireRule);
};
export const deleteFireRule = (id: number): Promise<BasicResponse> => {
return req.delete(`/system/rule/${id}`);
};

View File

@@ -6,6 +6,3 @@
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
export * from './tree';
export * from './template';

View File

@@ -0,0 +1,254 @@
<template>
<Layout>
<template #sidebar>
<div class="ks-sidebar-header">
<a-flex class="ks-sidebar-title">
<span class="icon"></span>
<span class="text">火力规则管理</span>
</a-flex>
<a-button class="ks-sidebar-add" size="small" @click="handleCreate">
<PlusOutlined />
新增
</a-button>
</div>
<a-list item-layout="horizontal" :data-source="datasource" class="ks-sidebar-list">
<template #renderItem="{ item }">
<a-list-item @click="()=> handleSelect(item)" :class="selectedFireRule?.id === item.id ? 'selected' : null">
<a-list-item-meta :description="substring(item.description,20)">
<template #title>
<span class="ks-algorithm-name">{{ substring(item.name, 20) }}</span>
<span class="ks-sidebar-list-type"><a-badge size="small" :count="getSceneTypeName(item)"></a-badge></span>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<a-pagination
v-model:current="query.pageNum"
:page-size="query.pageSize"
:total="datasourceTotal"
simple size="small" @change="handleChange" />
</template>
<div class="w-full h-full">
<a-card class="ks-page-card ks-algorithm-card">
<template #title>
<a-space>
<span class="point"></span>
<span class="text">规则配置</span>
</a-space>
</template>
<div class="ks-scrollable" style="height: 80.5vh;overflow-y: auto;padding-right: 10px">
<a-row :gutter="15">
<a-col :span="16">
<a-form
ref="formRef"
:label-col="{span: 6}"
:model="selectedFireRule"
autocomplete="off"
layout="horizontal"
name="basic"
>
<a-form-item
label="规则名称"
:rules="[{ required: true, message: '请输入规则名称!', trigger: ['input', 'change'] }]"
:name="['name']"
>
<a-input v-model:value="selectedFireRule.name" placeholder="请输入规则名称" />
</a-form-item>
<a-form-item
label="场景类型"
:name="['sceneType']"
>
<a-select v-model:value="selectedFireRule.sceneType" placeholder="请选择场景类型">
<a-select-option :value="null">通用</a-select-option>
<a-select-option :value="0">防御</a-select-option>
<a-select-option :value="1">空降</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="触发条件"
:rules="[{ required: true, message: '请输入触发条件!', trigger: ['input', 'change'] }]"
:name="['conditions']"
>
<a-input v-model:value="selectedFireRule.conditions" placeholder="请输入触发条件" />
</a-form-item>
<a-form-item
label="响应动作"
:rules="[{ required: true, message: '请输入响应动作!', trigger: ['input', 'change'] }]"
:name="['actions']"
>
<a-input v-model:value="selectedFireRule.actions" placeholder="请输入响应动作" />
</a-form-item>
<a-form-item
label="优先级(数值越小优先级越高)"
:rules="[{ required: true, message: '请输入优先级!', trigger: ['input', 'change'] }]"
:name="['priority']"
>
<a-input-number style="width:100%;" v-model:value="selectedFireRule.priority" placeholder="请输入优先级" />
</a-form-item>
<a-form-item
label="是否启用"
:name="['priority']"
>
<a-switch v-model:checked="selectedFireRule.enabled" />
</a-form-item>
<a-form-item
:wrapper-col="{offset: 6}"
>
<a-space>
<a-button @click="handleSave" type="primary">保存规则</a-button>
<a-popconfirm
v-if="selectedFireRule && selectedFireRule.id > 0"
title="确定删除?"
@confirm="handleDelete"
>
<a-button danger>删除规则</a-button>
</a-popconfirm>
</a-space>
</a-form-item>
</a-form>
</a-col>
</a-row>
</div>
</a-card>
</div>
</Layout>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { type FormInstance, message } from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import Layout from '../layout.vue';
import { createFireRule, deleteFireRule, findFireRuleByQuery, updateFireRule } from './api';
import type { FireRule, FireRuleRequest } from './types';
import { substring } from '@/utils/strings';
const query = ref<Partial<FireRuleRequest>>({
pageNum: 1,
pageSize: 10,
});
const defaultFireRule: FireRule = {
id: 0,
// 规则名称
name: null,
// 场景类型0-防御1-空降null表示通用
sceneType: null,
// 触发条件JSON格式
conditions: null,
// 响应动作JSON格式
actions: null,
// 优先级(数值越小优先级越高)
priority: 0,
// 是否启用0禁用1启用
enabled: true,
};
const getSceneTypeName = (item: FireRule): string => {
if (0 === item.sceneType) {
return '防御';
}
if (1 === item.sceneType) {
return '空降';
}
return '通用';
};
const resolveItem = (item: FireRule) => {
let newItem = JSON.parse(JSON.stringify(item));
return newItem;
};
const datasource = ref<FireRule[]>([]);
const datasourceTotal = ref<number>(0);
const selectedFireRule = ref<FireRule>(resolveItem(defaultFireRule));
const formRef = ref<FormInstance | null>(null);
const load = () => {
datasource.value = [];
datasourceTotal.value = 0;
formRef.value?.resetFields();
selectedFireRule.value = resolveItem(defaultFireRule);
findFireRuleByQuery(query.value).then(r => {
datasource.value = r.rows ?? [];
datasourceTotal.value = r.total ?? 0;
});
};
const handleCreate = () => {
selectedFireRule.value = resolveItem(defaultFireRule);
};
const handleSelect = (item: FireRule) => {
selectedFireRule.value = resolveItem(item);
formRef.value?.resetFields();
};
const handleDelete = () => {
if (selectedFireRule.value && selectedFireRule.value.id > 0) {
deleteFireRule(selectedFireRule.value.id).then(r => {
if (r.code === 200) {
load();
message.info(r.msg ?? '删除成功');
} else {
message.error(r.msg ?? '删除失败');
}
});
}
};
const handleSave = () => {
if (formRef.value) {
formRef.value.validate().then(() => {
let res = null;
let savedValue: FireRule = JSON.parse(JSON.stringify(selectedFireRule.value)) as any as FireRule;
if (savedValue.id > 0) {
res = updateFireRule(savedValue);
} else {
res = createFireRule(savedValue);
}
if (res) {
res.then(r => {
if (r.code === 200) {
load();
message.info(r.msg ?? '操作成功');
} else {
message.error(r.msg ?? '操作失败');
}
});
}
});
}
};
const handleChange = (page: number, pageSize: number) => {
query.value.pageNum = page;
query.value.pageSize = pageSize;
load();
};
onMounted(() => load());
</script>

View File

@@ -0,0 +1,34 @@
/*
* This file is part of the kernelstudio package.
*
* (c) 2014-2026 zlin <admin@kernelstudio.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
import type { NullableString, PageableResponse } from '@/types';
export interface FireRule {
id: number,
// 规则名称
name: NullableString,
// 场景类型0-防御1-空降null表示通用
sceneType: Number | null,
// 触发条件JSON格式
conditions: NullableString,
// 响应动作JSON格式
actions: NullableString,
// 优先级(数值越小优先级越高)
priority: number,
// 是否启用0禁用1启用
enabled: boolean,
}
export interface FireRuleRequest extends FireRule {
pageNum: number,
pageSize: number,
}
export interface FireRulePageableResponse extends PageableResponse<FireRule> {
}

View File

@@ -12,7 +12,6 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AAvatar: typeof import('ant-design-vue/es')['Avatar']
ABadge: typeof import('ant-design-vue/es')['Badge']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
@@ -23,7 +22,6 @@ declare module 'vue' {
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AEmpty: typeof import('ant-design-vue/es')['Empty']
AFlex: typeof import('ant-design-vue/es')['Flex']
AFloatButton: typeof import('ant-design-vue/es')['FloatButton']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest']
@@ -46,8 +44,6 @@ declare module 'vue' {
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpace: typeof import('ant-design-vue/es')['Space']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ATable: typeof import('ant-design-vue/es')['Table']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
ATabs: typeof import('ant-design-vue/es')['Tabs']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
@@ -59,7 +55,6 @@ declare module 'vue' {
// For TSX support
declare global {
const AAvatar: typeof import('ant-design-vue/es')['Avatar']
const ABadge: typeof import('ant-design-vue/es')['Badge']
const AButton: typeof import('ant-design-vue/es')['Button']
const ACard: typeof import('ant-design-vue/es')['Card']
@@ -70,7 +65,6 @@ declare global {
const ADropdown: typeof import('ant-design-vue/es')['Dropdown']
const AEmpty: typeof import('ant-design-vue/es')['Empty']
const AFlex: typeof import('ant-design-vue/es')['Flex']
const AFloatButton: typeof import('ant-design-vue/es')['FloatButton']
const AForm: typeof import('ant-design-vue/es')['Form']
const AFormItem: typeof import('ant-design-vue/es')['FormItem']
const AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest']
@@ -93,8 +87,6 @@ declare global {
const ASelect: typeof import('ant-design-vue/es')['Select']
const ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
const ASpace: typeof import('ant-design-vue/es')['Space']
const ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
const ATable: typeof import('ant-design-vue/es')['Table']
const ATabPane: typeof import('ant-design-vue/es')['TabPane']
const ATabs: typeof import('ant-design-vue/es')['Tabs']
const ATextarea: typeof import('ant-design-vue/es')['Textarea']

Some files were not shown because too many files have changed in this diff Show More