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
This commit is contained in:
MHW
2026-03-13 14:41:41 +08:00
47 changed files with 971 additions and 135 deletions

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;
}

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;
}
}

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

@@ -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

@@ -1204,7 +1204,7 @@
.ant-tabs-content {
//padding: 15px;
padding: 4px;
background: #041b36db;
background: #041832;
}
&.settings-tab,
@@ -1642,3 +1642,9 @@
cursor: pointer;
}
}
.ant-input-group-addon {
.anticon{
color: #eeeeee;
}
}

View File

@@ -74,6 +74,7 @@ export interface GraphTaskElement extends BaseElement {
templateType: NullableString,
inputs: any;
outputs: any;
order: number;
variables: ElementVariable[];
parameters: ElementParameter[];
children?: GraphTaskElement[],

View File

@@ -52,7 +52,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,
});

View File

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

View File

@@ -9,25 +9,30 @@
>
<template #title>
<a-space>
<span class="ks-designer-node-icon"></span>
<span class="ks-designer-node-title">{{ element?.name ?? '-' }}</span>
</a-space>
</template>
<div class="port port-in" data-port="in-0" magnet="passive"></div>
<div class="w-full">
<div class="port port-in">
<div class="triangle-left" data-port="in-0" magnet="passive"></div>
</div>
<div class="w-full ks-designer-node-text">
<a-tooltip v-if="(element?.description ?? (element?.name ?? '-')).length >= 38">
<template #title>
{{ element?.description ?? element?.name }}
</template>
<p>
<p class="ks-designer-node-label">
{{ substring(element?.description ?? (element?.name ?? '-'), 40) }}
</p>
</a-tooltip>
<p v-else>
<p v-else class="ks-designer-node-label">
{{ substring(element?.description ?? (element?.name ?? '-'), 40) }}
</p>
</div>
<div class="port port-out" data-port="out-0" magnet="active"></div>
<div class="port port-out">
<div class="triangle-right" data-port="out-0" magnet="active"></div>
</div>
</a-card>
<template #overlay>
@@ -147,18 +152,18 @@ 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%));
}
@@ -168,14 +173,15 @@ export default defineComponent({
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;
font-size: 12px;
color: #fff;
color: #312e2e;
margin-top: -7px;
display: block;
}
.ant-card-body {
@@ -183,7 +189,7 @@ export default defineComponent({
height: calc(100% - 25px);
border-radius: 0;
font-size: 12px;
padding: 8px 15px !important;
padding: 10px 30px !important;
border-top: 1px solid rgba(108, 99, 255, 0.5);
overflow: hidden;
text-overflow: ellipsis;
@@ -197,69 +203,6 @@ 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 {
@@ -286,6 +229,31 @@ 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: 5px solid transparent;
border-right: 10px solid #3c82f6;
border-bottom: 6px solid transparent;
position: absolute;
left: -8px;
top: 2px;
}
/* 右三角形 */
.triangle-right {
width: 0;
height: 0;
border-top: 5px solid transparent;
border-left: 10px solid #3c82f6;
border-bottom: 6px solid transparent;
position: absolute;
right: -8px;
top: 2px;
}
}
// 左侧入桩样式
@@ -302,7 +270,7 @@ export default defineComponent({
position: absolute;
//top: 7px;
left: -8px;
left: 10px;
top: 50%;
}
@@ -320,7 +288,7 @@ export default defineComponent({
//right: 8px;
//top: 7px;
top: 50%;
right: -12px;
right: 6px;
}
@@ -332,5 +300,95 @@ export default defineComponent({
text-overflow: ellipsis;
//white-space: nowrap;
}
&.ks-designer-root-node{
.ant-card-head {
background: url('@/assets/icons/card-head-gray.png') center / 100% 100%;
}
.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{
.ant-card-head {
background: url('@/assets/icons/card-head-green.png') center / 100% 100%;
}
.ks-designer-node-icon {
background: url('@/assets/icons/icon-sequence.svg') center / 100% 100%;
}
}
&.ks-designer-parallel-node{
.ant-card-head {
background: url('@/assets/icons/card-head-blue.png') center / 100% 100%;
}
.ks-designer-node-icon {
background: url('@/assets/icons/icon-parallel.svg') center / 100% 100%;
}
}
&.ks-designer-precondition-node{
.ant-card-head {
background: url('@/assets/icons/card-head-dark.png') center / 100% 100%;
}
.ks-designer-node-icon {
background: url('@/assets/icons/icon-branch.svg') center / 100% 100%;
}
}
//&.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

@@ -18,6 +18,10 @@ export const createGraphTaskElement = (element: GraphTaskElement, width: number
if (!realHeight) {
realHeight = 120;
}
// if(element.category === 'precondition') {
// width = 100;
// realHeight = 100;
// }
return {
shape: 'task',
id: element.key,

View File

@@ -101,10 +101,10 @@ 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);
}
});
}

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;"
<a-form
v-if="currentElement.parameters && currentElement.parameters.length > 0"
autocomplete="off"
layout="vertical"
name="basic"
style="padding-bottom:15px;"
>
<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-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>

View File

@@ -26,6 +26,7 @@ export const createGraphTaskElementFromTemplate = (
name: template.name,
category: template.type,
description: template.description,
order: 0,
position: {
x: realRect.x ?? 0,
y: realRect.y ?? 0,

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']

39
solution-rule/pom.xml Normal file
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-rule</artifactId>
<description>
rule模块
</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,32 @@
package com.solution.rule.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 平台挂载的组件实体类
* 对应表 platform_component
*/
@Data
@ApiModel(value = "平台组件对象", description = "平台所挂载的武器、雷达、通信等组件")
public class PlatformComponent {
@ApiModelProperty(value = "组件ID主键自增")
private Integer id;
@ApiModelProperty(value = "组件名称(具体型号)")
private String name;
@ApiModelProperty(value = "组件类型weapon武器、radar雷达、comm通信")
private String type;
@ApiModelProperty(value = "组件描述信息")
private String description;
@ApiModelProperty(value = "所属平台ID关联platform表")
private Integer platformId;
@ApiModelProperty(value = "组件数量")
private Long num;
}

View File

@@ -0,0 +1,14 @@
package com.solution.rule.domain;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.WeaponModelVO;
import lombok.Data;
@Data
public class RuleParam {
private WeaponModelVO weaponModelVO;
private WeaponModelDTO weaponModelDTO;
}

View File

@@ -0,0 +1,22 @@
package com.solution.rule.domain.dto;
import lombok.Data;
/**
* 规则请求参数
*/
@Data
public class RequestDTO {
//编队数量
private Long formationNum;
//武装直升机数量
private Long gunshipNum;
//无人机数量
private Long droneNum;
//单兵武器数量
private Long singleWeaponNum;
}

View File

@@ -0,0 +1,23 @@
package com.solution.rule.domain.dto;
import com.solution.rule.domain.PlatformComponent;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
public class WeaponModelDTO {
@ApiModelProperty("平台id")
private Integer id;
@ApiModelProperty("平台名称")
private String name;
@ApiModelProperty("平台描述")
private String description;
@ApiModelProperty("平台组件")
private List<PlatformComponent> components;
}

View File

@@ -0,0 +1,16 @@
package com.solution.rule.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
// 组件计数VO
@Data
@ApiModel("组件计数信息")
public class ComponentCountVO {
@ApiModelProperty("组件名称")
private String componentName;
@ApiModelProperty("组件数量")
private Long count;
}

View File

@@ -0,0 +1,17 @@
package com.solution.rule.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("平台组件名称聚合")
public class PlatformComponentNamesVO {
@ApiModelProperty("平台名称")
private String platformName;
@ApiModelProperty("该平台下的组件名称列表(去重)")
private List<String> componentNames;
}

View File

@@ -0,0 +1,18 @@
package com.solution.rule.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
// 平台武器聚合VO
@Data
@ApiModel("平台武器聚合信息")
public class PlatformWeaponAggregateVO {
@ApiModelProperty("平台名称")
private String platformName;
@ApiModelProperty("该平台下的组件列表")
private List<ComponentCountVO> components;
}

View File

@@ -0,0 +1,19 @@
package com.solution.rule.domain.vo;
import com.solution.rule.domain.dto.WeaponModelDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class WeaponModelVO {
@ApiModelProperty("平台名称")
private String platformName;
@ApiModelProperty("组件名称")
private String componentName;
@ApiModelProperty("组件数量")
private Long count;
}

View File

@@ -0,0 +1,39 @@
package com.solution.rule.enums;
public enum SceneType {
DEFENSE(0, "防御"),
AIRBORNE(1, "空降");
private final int code;
private final String description;
SceneType(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 根据数字编码获取对应的枚举
* @param code 前端传递的数字
* @return 枚举实例
* @throws IllegalArgumentException 如果找不到对应枚举
*/
public static SceneType fromCode(int code) {
for (SceneType type : values()) {
if (type.code == code) {
return type;
}
}
throw new IllegalArgumentException("未知的场景类型编码: " + code);
}
}

View File

@@ -0,0 +1,43 @@
package com.solution.rule.handler;
import cn.hutool.core.util.ObjectUtil;
import com.solution.rule.domain.RuleParam;
/**
* 规则链抽象类
*/
public abstract class AbstractRuleChainHandler {
private AbstractRuleChainHandler nextHandler;
/**
* 执行过滤方法
* @param ruleParam
* @return
*/
public abstract RuleParam doHandler(RuleParam ruleParam);
/**
* 执行下一个处理器
* @param ruleParam
* @return
*/
public RuleParam doNextHandler(RuleParam ruleParam){
if(ObjectUtil.isEmpty(nextHandler) || ObjectUtil.isNotEmpty(ruleParam)){
return ruleParam;
}
return nextHandler.doHandler(ruleParam);
}
/**
* 设置下游handler
* @param nextHandler
*/
public void setNextHandler(AbstractRuleChainHandler nextHandler) {
this.nextHandler = nextHandler;
}
}

View File

@@ -0,0 +1,47 @@
package com.solution.rule.handler;
import cn.hutool.core.collection.CollUtil;
import com.solution.common.constant.ExceptionConstants;
import com.solution.rule.domain.RuleParam;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
/**
* 封装规则处理链
*/
@Component
public class RuleChainHandler {
@Resource
private List<AbstractRuleChainHandler> chainHandlers;
private AbstractRuleChainHandler firstHandler;
/**
* 组装处理链
*/
@PostConstruct
private void constructChain(){
if (CollUtil.isEmpty(chainHandlers)) {
throw new RuntimeException(ExceptionConstants.NOT_FOUND_CARRIAGE_CHAIN_HANDLER);
}
this.firstHandler = chainHandlers.get(0);
for (int i = 0; i < chainHandlers.size(); i++) {
if(i == (chainHandlers.size() - 1)){
chainHandlers.get(i).setNextHandler(null);
}else {
chainHandlers.get(i).setNextHandler(chainHandlers.get(i + 1));
}
}
}
public RuleParam findRuleParam(RuleParam ruleParam){
return firstHandler.doHandler(ruleParam);
}
}

View File

@@ -0,0 +1,36 @@
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.WeaponModelVO;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* 战斗机处理链
*/
@Component
public class WarplaneHandler extends AbstractRuleChainHandler{
@Override
public RuleParam doHandler(RuleParam ruleParam) {
if(ObjectUtil.isEmpty(ruleParam) || ObjectUtil.isEmpty(ruleParam.getWeaponModelDTO())){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
WeaponModelDTO weaponModelDTO = ruleParam.getWeaponModelDTO();
List<PlatformComponent> platformComponents = weaponModelDTO.getComponents().stream()
.filter(component -> PlatformAndModuleConstants.F22.equals(component.getName()))
.collect(Collectors.toList());
if (CollUtil.isEmpty(platformComponents)) {
throw new RuntimeException(ExceptionConstants.NOT_FOUND_F22_COMPONENT);
}
return null;
}
}

View File

@@ -0,0 +1,9 @@
package com.solution.rule.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ModelDetailMapper {
}

View File

@@ -0,0 +1,19 @@
package com.solution.rule.mapper;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.WeaponModelVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface RuleMapper {
/**
* 获取所有武器平台和组件
* @return
*/
List<WeaponModelVO> getWeapon();
List<WeaponModelVO> getPlatformComponentNames();
}

View File

@@ -0,0 +1,29 @@
package com.solution.rule.service;
import com.solution.rule.domain.dto.RequestDTO;
import com.solution.rule.domain.dto.WeaponModelDTO;
import com.solution.rule.domain.vo.PlatformComponentNamesVO;
import com.solution.rule.domain.vo.PlatformWeaponAggregateVO;
import com.solution.rule.domain.vo.WeaponModelVO;
import java.util.HashMap;
import java.util.List;
public interface RuleService {
/**
* 开始执行规则匹配
* @param sceneType
* @param weaponModelDTO
* @return
*/
WeaponModelVO execute(Integer sceneType, WeaponModelDTO weaponModelDTO);
List<PlatformWeaponAggregateVO> getWeapon();
/**
* 获取所有武器平台和组件
* @return
*/
List<PlatformComponentNamesVO> getPlatformComponentNames();
}

View File

@@ -0,0 +1,109 @@
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.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.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.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class RuleServiceImpl implements RuleService {
@Autowired
private SceneStrategyFactory strategyFactory;
@Autowired
private RuleMapper ruleMapper;
@Override
public WeaponModelVO execute(Integer sceneType, WeaponModelDTO weaponModelDTO) {
if(ObjectUtil.isNull(sceneType) || ObjectUtil.isEmpty(weaponModelDTO)){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
//TODO 查数据库获取我方装备
List<PlatformWeaponAggregateVO> weapon = this.getWeapon();
SceneStrategy strategy = strategyFactory.getStrategy(sceneType);
WeaponModelVO result = strategy.execute(weaponModelDTO);
if(ObjectUtil.isEmpty(result)){
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
}
return result;
}
@Override
public List<PlatformWeaponAggregateVO> getWeapon() {
List<WeaponModelVO> flatList = ruleMapper.getWeapon();
if (CollUtil.isEmpty(flatList)) {
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
}
Map<String, List<WeaponModelVO>> groupByPlatform = flatList.stream()
.collect(Collectors.groupingBy(WeaponModelVO::getPlatformName));
List<PlatformWeaponAggregateVO> result = new ArrayList<>();
for (Map.Entry<String, List<WeaponModelVO>> entry : groupByPlatform.entrySet()) {
PlatformWeaponAggregateVO platformVO = new PlatformWeaponAggregateVO();
platformVO.setPlatformName(entry.getKey());
List<ComponentCountVO> components = entry.getValue().stream()
.map(item -> {
ComponentCountVO comp = new ComponentCountVO();
comp.setComponentName(item.getComponentName());
comp.setCount(item.getCount());
return comp;
})
.collect(Collectors.toList());
platformVO.setComponents(components);
result.add(platformVO);
}
return result;
}
/**
* 获取所有武器平台和组件
* @return
*/
@Override
public List<PlatformComponentNamesVO> getPlatformComponentNames() {
List<WeaponModelVO> flatList = ruleMapper.getPlatformComponentNames();
if (CollUtil.isEmpty(flatList)) {
throw new RuntimeException(ExceptionConstants.RESULT_EXCEPTION);
}
Map<String, List<String>> groupByPlatform = flatList.stream()
.collect(Collectors.groupingBy(
WeaponModelVO::getPlatformName,
Collectors.mapping(WeaponModelVO::getComponentName, Collectors.toList())
));
return groupByPlatform.entrySet().stream()
.map(entry -> {
PlatformComponentNamesVO vo = new PlatformComponentNamesVO();
vo.setPlatformName(entry.getKey());
vo.setComponentNames(entry.getValue());
return vo;
})
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,43 @@
package com.solution.rule.strategy;
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.enums.SceneType;
import com.solution.rule.handler.RuleChainHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AirborneStrategy implements SceneStrategy{
@Autowired
private RuleChainHandler ruleChainHandler;
/**
* 空降场景处理
* @param weaponModelDTO
* @return
*/
@Override
public WeaponModelVO execute(WeaponModelDTO weaponModelDTO) {
if(ObjectUtil.isEmpty(weaponModelDTO)){
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();
}
@Override
public SceneType getSceneType() {
return SceneType.AIRBORNE;
}
}

View File

@@ -0,0 +1,42 @@
package com.solution.rule.strategy;
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.enums.SceneType;
import com.solution.rule.handler.RuleChainHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DefenseStrategy implements SceneStrategy{
@Autowired
private RuleChainHandler ruleChainHandler;
/**
* 防御场景处理
* @param weaponModelDTO
* @return
*/
@Override
public WeaponModelVO execute(WeaponModelDTO weaponModelDTO) {
if(ObjectUtil.isEmpty(weaponModelDTO)){
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();
}
@Override
public SceneType getSceneType() {
return SceneType.DEFENSE;
}
}

View File

@@ -0,0 +1,15 @@
package com.solution.rule.strategy;
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.enums.SceneType;
import java.util.HashMap;
public interface SceneStrategy {
WeaponModelVO execute(WeaponModelDTO weaponModelDTO);
SceneType getSceneType();
}

View File

@@ -0,0 +1,45 @@
package com.solution.rule.strategy;
import com.solution.rule.enums.SceneType;
import com.solution.rule.strategy.SceneStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@Component
public class SceneStrategyFactory {
@Autowired
private List<SceneStrategy> strategyList;
private final Map<SceneType, SceneStrategy> strategyMap = new EnumMap<>(SceneType.class);
@PostConstruct
public void init() {
for (SceneStrategy strategy : strategyList) {
SceneType type = strategy.getSceneType();
if (strategyMap.containsKey(type)) {
throw new IllegalStateException("重复的场景类型: " + type);
}
strategyMap.put(type, strategy);
}
}
/**
* 根据前端传递的数字编码获取对应的策略
* @param code 前端传递的数字
* @return 策略实现
* @throws IllegalArgumentException 如果编码无效或策略未注册
*/
public SceneStrategy getStrategy(int code) {
SceneType type = SceneType.fromCode(code);
SceneStrategy strategy = strategyMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("未找到编码 " + code + " 对应的策略实现");
}
return strategy;
}
}

View File

@@ -0,0 +1,37 @@
<?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="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>
<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>
</mapper>