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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BIN
modeler/src/assets/icons/card-head-blue.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
modeler/src/assets/icons/card-head-dark.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
modeler/src/assets/icons/card-head-gray.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
modeler/src/assets/icons/card-head-green.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
modeler/src/assets/icons/card-head-red.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
modeler/src/assets/icons/icon-action.svg
Normal 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 |
1
modeler/src/assets/icons/icon-branch.svg
Normal 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 |
1
modeler/src/assets/icons/icon-node.svg
Normal 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 |
1
modeler/src/assets/icons/icon-parallel.svg
Normal 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 |
1
modeler/src/assets/icons/icon-root.svg
Normal 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 |
1
modeler/src/assets/icons/icon-sequence.svg
Normal 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 |
1
modeler/src/assets/icons/lx.svg
Normal 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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ export interface GraphTaskElement extends BaseElement {
|
||||
templateType: NullableString,
|
||||
inputs: any;
|
||||
outputs: any;
|
||||
order: number;
|
||||
variables: ElementVariable[];
|
||||
parameters: ElementParameter[];
|
||||
children?: GraphTaskElement[],
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -28,6 +28,10 @@ export const createLineOptions = (): any => {
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: ' ',
|
||||
strokeDashoffset: 0,
|
||||
|
||||
// 去掉箭头
|
||||
targetMarker: null,
|
||||
sourceMarker: null,
|
||||
},
|
||||
marker: {
|
||||
fill: '#5da0df',
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
8
modeler/types/components.d.ts
vendored
@@ -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
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.solution.rule.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ModelDetailMapper {
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
37
solution-rule/src/main/resources/mapper/rule/RuleMapper.xml
Normal 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>
|
||||