旧接口字段更新BUG修复

This commit is contained in:
MHW
2026-04-20 09:38:58 +08:00
parent 39b04d8b73
commit ae01a2aa01
9 changed files with 509 additions and 36 deletions

View File

@@ -9,6 +9,7 @@ import com.solution.rule.domain.Rule;
import com.solution.rule.domain.config.RuleConfig;
import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.domain.config.RuleParamMeta;
import com.solution.rule.domain.config.vo.RuleFourBlocksGraphVO;
import com.solution.rule.domain.config.vo.RuleGraphVO;
import com.solution.rule.service.IRuleService;
import com.solution.rule.service.IRuleConfigService;
@@ -88,6 +89,14 @@ import java.util.List;
return success(graph);
}
@PreAuthorize("@ss.hasPermi('system:rule:list')")
@GetMapping("/config/graph/four-blocks")
@ApiOperation("四块规则知识图谱(装备/目标/阵位/航迹;参数值与运行时 globalParams 一致)")
public AjaxResult configGraphFourBlocks() {
RuleFourBlocksGraphVO vo = ruleConfigService.buildFourBlocksKnowledgeGraph();
return success(vo);
}
@PreAuthorize("@ss.hasPermi('system:rule:query')")
@GetMapping("/config/{ruleCode}")
@ApiOperation("查询规则聚合详情")

View File

@@ -18,7 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete>
<sql id="selectNodeparameterVo">
select id, treeId, node_instance_id, param_def_id,`value`, group_index from nodeparameter
select id, tree_id, node_instance_id, param_def_id,`value`, group_index from nodeparameter
</sql>
<select id="selectNodeparameterList" parameterType="Nodeparameter" resultMap="NodeparameterResult">
@@ -28,7 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="treeId != null"> and tree_id = #{treeId}</if>
<if test="nodeInstanceId != null "> and node_instance_id = #{nodeInstanceId}</if>
<if test="paramDefId != null "> and param_def_id = #{paramDefId}</if>
<if test="value != null and value != ''"> and value = #{value}</if>
<if test="value != null and value != ''"> and `value` = #{value}</if>
<if test="groupIndex != null"> and group_index = #{groupIndex}</if>
</where>
</select>

View File

@@ -18,6 +18,12 @@ public class RuleGraphVO implements Serializable {
@ApiModelProperty("边列表")
private List<RuleGraphEdgeVO> edges;
@ApiModelProperty("前端布局提示:如 radial_hub 使用径向圆心布局")
private String layoutHint;
@ApiModelProperty("径向布局时的中心节点 id通常为 drools_facade")
private String focusNodeId;
public List<RuleGraphNodeVO> safeNodes() {
if (nodes == null) {
nodes = new ArrayList<>();

View File

@@ -4,6 +4,7 @@ import com.solution.rule.domain.config.RuleConfig;
import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.domain.config.RuleDictItem;
import com.solution.rule.domain.config.RuleParamMeta;
import com.solution.rule.domain.config.vo.RuleFourBlocksGraphVO;
import com.solution.rule.domain.config.vo.RuleGraphVO;
import java.util.List;
@@ -31,4 +32,9 @@ public interface IRuleConfigService {
* 根据当前页规则主数据构建知识图谱(节点与边),参数与任务类型从库批量加载。
*/
RuleGraphVO buildKnowledgeGraph(List<RuleConfig> ruleConfigs);
/**
* 四块规则知识图谱(装备/目标/阵位/航迹):中枢 If/Then + 环形参数,参数值与 {@link #loadEnabledGlobalParams()} 一致。
*/
RuleFourBlocksGraphVO buildFourBlocksKnowledgeGraph();
}

View File

@@ -9,6 +9,12 @@ import com.solution.rule.domain.config.RuleConfigQuery;
import com.solution.rule.domain.config.RuleConfigTaskTypeRow;
import com.solution.rule.domain.config.RuleDictItem;
import com.solution.rule.domain.config.RuleParamMeta;
import com.solution.rule.domain.config.graph.RuleFourBlockDefinition;
import com.solution.rule.domain.config.graph.RuleFourBlockRuleOutputCatalog;
import com.solution.rule.domain.config.graph.RuleParamOutputHint;
import com.solution.rule.domain.config.vo.RuleFourBlockClusterVO;
import com.solution.rule.domain.config.vo.RuleFourBlockParamRowVO;
import com.solution.rule.domain.config.vo.RuleFourBlocksGraphVO;
import com.solution.rule.domain.config.vo.RuleGraphEdgeVO;
import com.solution.rule.domain.config.vo.RuleGraphNodeVO;
import com.solution.rule.domain.config.vo.RuleGraphVO;
@@ -20,6 +26,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -29,18 +36,6 @@ public class RuleConfigServiceImpl implements IRuleConfigService {
private static final Pattern RULE_SLOT_KEYS = Pattern.compile("^(blueRuleKeywords|redRuleKeywords|ruleScore)_\\d+$");
private static final boolean ALLOW_UNKNOWN_PARAM_KEY = false;
private static final Map<String, String> MODULE_LABELS;
static {
Map<String, String> m = new LinkedHashMap<>();
m.put("equipment", "装备匹配");
m.put("target", "目标分配");
m.put("position", "阵位部署");
m.put("track", "航迹生成");
m.put("group", "编组");
MODULE_LABELS = Collections.unmodifiableMap(m);
}
@Autowired
private RuleConfigMapper ruleConfigMapper;
@Autowired
@@ -187,19 +182,20 @@ public class RuleConfigServiceImpl implements IRuleConfigService {
Map<String, Object> levelPayload = new LinkedHashMap<>();
levelPayload.put("dictType", "level");
levelPayload.put("dictCode", levelCode);
putNode(nodeById, levelId, node(levelId, dictLabel(levelDict, levelCode, levelCode), "level", levelPayload));
levelPayload.put("dictName", dictLabel(levelDict, levelCode, null));
putNode(nodeById, levelId, node(levelId, levelCode, "level", levelPayload));
String kindId = "kind:" + levelCode + ":" + kindCode;
Map<String, Object> kindPayload = new LinkedHashMap<>();
kindPayload.put("levelCode", levelCode);
kindPayload.put("kindCode", kindCode);
putNode(nodeById, kindId, node(kindId, dictLabel(kindDict, kindCode, kindCode), "kind", kindPayload));
kindPayload.put("dictName", dictLabel(kindDict, kindCode, null));
putNode(nodeById, kindId, node(kindId, kindCode, "kind", kindPayload));
String moduleId = "module:" + moduleCode;
String moduleLabel = MODULE_LABELS.getOrDefault(moduleCode, moduleCode);
Map<String, Object> modulePayload = new LinkedHashMap<>();
modulePayload.put("moduleCode", moduleCode);
putNode(nodeById, moduleId, node(moduleId, moduleLabel, "module", modulePayload));
putNode(nodeById, moduleId, node(moduleId, moduleCode, "module", modulePayload));
String ruleId = "rule:" + ruleCode;
String ruleLabel = ObjectUtil.defaultIfBlank(rule.getRuleName(), ruleCode);
@@ -248,7 +244,8 @@ public class RuleConfigServiceImpl implements IRuleConfigService {
String taskId = "task:" + tt;
Map<String, Object> taskPayload = new LinkedHashMap<>();
taskPayload.put("taskTypeCode", tt);
putNode(nodeById, taskId, node(taskId, dictLabel(taskTypeDict, tt, tt), "taskType", taskPayload));
taskPayload.put("dictName", dictLabel(taskTypeDict, tt, null));
putNode(nodeById, taskId, node(taskId, tt, "taskType", taskPayload));
addEdge(edges, edgeIds, "applies_task:" + ruleCode + ":" + tt, ruleId, taskId, "rule_applies_task", null);
}
}
@@ -279,6 +276,281 @@ public class RuleConfigServiceImpl implements IRuleConfigService {
return graph;
}
@Override
public RuleFourBlocksGraphVO buildFourBlocksKnowledgeGraph() {
RuleFourBlocksGraphVO out = new RuleFourBlocksGraphVO();
Map<String, Object> globalPreview = loadEnabledGlobalParams();
out.setGlobalParamsPreview(new LinkedHashMap<>(globalPreview));
RuleConfigQuery q = new RuleConfigQuery();
q.setEnabled(1);
List<RuleConfig> allEnabled = ruleConfigMapper.selectRuleConfigList(q);
if (CollUtil.isEmpty(allEnabled)) {
out.setBlocks(new ArrayList<>());
return out;
}
Set<String> fourModules = new HashSet<>(Arrays.asList("equipment", "target", "position", "track"));
Map<String, List<RuleConfig>> byModule = allEnabled.stream()
.filter(r -> r != null && ObjectUtil.isNotEmpty(r.getModuleCode()) && fourModules.contains(r.getModuleCode()))
.collect(Collectors.groupingBy(RuleConfig::getModuleCode, LinkedHashMap::new, Collectors.toList()));
List<RuleFourBlockClusterVO> blocks = new ArrayList<>();
for (RuleFourBlockDefinition def : RuleFourBlockDefinition.ordered()) {
List<RuleConfig> moduleRules = new ArrayList<>(byModule.getOrDefault(def.getModuleCode(), Collections.emptyList()));
moduleRules.sort(Comparator.comparing(RuleConfig::getPriorityNo, Comparator.nullsLast(Integer::compareTo)));
List<String> ruleCodes = moduleRules.stream().map(RuleConfig::getRuleCode).filter(ObjectUtil::isNotEmpty).collect(Collectors.toList());
List<RuleConfigParam> plist = ruleCodes.isEmpty()
? Collections.emptyList()
: ruleConfigMapper.selectParamsByRuleCodes(ruleCodes);
Map<String, Integer> priByRule = moduleRules.stream()
.filter(r -> ObjectUtil.isNotEmpty(r.getRuleCode()))
.collect(Collectors.toMap(RuleConfig::getRuleCode, RuleConfig::getPriorityNo, (a, b) -> a));
plist.sort(Comparator
.comparing((RuleConfigParam p) -> priByRule.getOrDefault(p.getRuleCode(), Integer.MAX_VALUE))
.thenComparing(p -> p.getSortNo() == null ? Integer.MAX_VALUE : p.getSortNo())
.thenComparing(RuleConfigParam::getParamKey, Comparator.nullsLast(String::compareTo)));
Map<String, RuleConfig> ruleByCode = moduleRules.stream()
.filter(r -> r != null && ObjectUtil.isNotEmpty(r.getRuleCode()))
.collect(Collectors.toMap(RuleConfig::getRuleCode, Function.identity(), (a, b) -> a));
RuleFourBlockClusterVO cluster = new RuleFourBlockClusterVO();
cluster.setBlockId(def.getBlockId());
cluster.setModuleCode(def.getModuleCode());
cluster.setTitle(def.getDroolsRuleName());
cluster.setDroolsRuleName(def.getDroolsRuleName());
cluster.setSalience(def.getSalience());
cluster.setWhenExpr(def.getWhenExpr());
cluster.setThenAction(def.getThenAction());
cluster.setParamRows(buildFourBlockParamRows(def, ruleByCode, plist, globalPreview));
cluster.setGraph(buildFourBlockNeoStyleGraph(def, moduleRules, plist, globalPreview, ruleByCode));
blocks.add(cluster);
}
out.setBlocks(blocks);
return out;
}
private List<RuleFourBlockParamRowVO> buildFourBlockParamRows(RuleFourBlockDefinition def,
Map<String, RuleConfig> ruleByCode,
List<RuleConfigParam> plist,
Map<String, Object> globalPreview) {
List<RuleFourBlockParamRowVO> rows = new ArrayList<>();
if (CollUtil.isEmpty(plist)) {
return rows;
}
String moduleCode = def.getModuleCode();
for (RuleConfigParam p : plist) {
if (p == null || ObjectUtil.isEmpty(p.getParamKey()) || ObjectUtil.isEmpty(p.getRuleCode())) {
continue;
}
if (p.getEnabled() != null && p.getEnabled() == 0) {
continue;
}
RuleConfig r = ruleByCode.get(p.getRuleCode());
if (r == null) {
continue;
}
RuleFourBlockParamRowVO row = new RuleFourBlockParamRowVO();
row.setRuleCode(r.getRuleCode());
row.setRuleName(r.getRuleName());
row.setParamKey(p.getParamKey());
row.setParamName(ObjectUtil.defaultIfBlank(p.getParamName(), p.getParamKey()));
row.setWhenText(formatParamWhenForRow(r, def));
row.setThenText(formatParamThenForRow(r, def));
row.setOutputText(formatParamOutputForRow(p, globalPreview, moduleCode));
rows.add(row);
}
return rows;
}
private static String formatParamWhenForRow(RuleConfig r, RuleFourBlockDefinition def) {
if (r != null && ObjectUtil.isNotEmpty(r.getConditionExpr())) {
return r.getConditionExpr();
}
return ObjectUtil.defaultIfBlank(def.getWhenExpr(), "");
}
private static String formatParamThenForRow(RuleConfig r, RuleFourBlockDefinition def) {
if (r != null && ObjectUtil.isNotEmpty(r.getActionExpr())) {
return r.getActionExpr();
}
return ObjectUtil.defaultIfBlank(def.getThenAction(), "");
}
private static String formatParamOutputForRow(RuleConfigParam p, Map<String, Object> globalPreview, String moduleCode) {
Object ev = globalPreview != null ? globalPreview.get(p.getParamKey()) : null;
String hint = RuleParamOutputHint.effectLine(p.getParamKey(), moduleCode);
if (ev == null) {
return hint;
}
return "生效值:" + String.valueOf(ev) + "" + hint;
}
/**
* Neo4j 风格子图:门面节点、多条 rule_item、参数按归属规则展开含 drools_contains / rule_priority_next / rule_has_param 边。
*/
private RuleGraphVO buildFourBlockNeoStyleGraph(RuleFourBlockDefinition def,
List<RuleConfig> moduleRules,
List<RuleConfigParam> plist,
Map<String, Object> globalPreview,
Map<String, RuleConfig> ruleByCode) {
RuleGraphVO graph = new RuleGraphVO();
List<RuleGraphNodeVO> nodes = graph.safeNodes();
List<RuleGraphEdgeVO> edges = graph.safeEdges();
Set<String> edgeIds = new LinkedHashSet<>();
String bid = def.getBlockId();
String droolsId = "four:block:" + bid + ":drools";
List<String> stepLabels = def.getComputationSteps();
String lastStepId = null;
for (int i = 0; i < stepLabels.size(); i++) {
String text = stepLabels.get(i);
if (ObjectUtil.isEmpty(text)) {
continue;
}
String sid = "four:block:" + bid + ":step:" + i;
Map<String, Object> sp = new LinkedHashMap<>();
sp.put("stepIndex", i);
sp.put("blockId", bid);
nodes.add(node(sid, text, "compute_step", sp));
if (lastStepId != null) {
addEdge(edges, edgeIds, "four:compute_flow:" + bid + ":" + (i - 1),
lastStepId, sid, "compute_flow", "");
}
lastStepId = sid;
}
Map<String, Object> droolsPayload = new LinkedHashMap<>();
droolsPayload.put("blockId", bid);
droolsPayload.put("salience", def.getSalience());
droolsPayload.put("droolsRuleName", def.getDroolsRuleName());
droolsPayload.put("computationHint", def.getDroolsRuleName() + ":业务步骤汇总后与库表规则项、参数一并展示");
nodes.add(node(droolsId, def.getDroolsRuleName(), "drools_facade", droolsPayload));
if (lastStepId != null) {
addEdge(edges, edgeIds, "four:compute_flow:to_drools:" + bid,
lastStepId, droolsId, "compute_flow", "汇总");
}
Set<String> ruleCodeSet = new HashSet<>();
List<String> ruleNodeIds = new ArrayList<>();
for (RuleConfig r : moduleRules) {
if (r == null || ObjectUtil.isEmpty(r.getRuleCode())) {
continue;
}
ruleCodeSet.add(r.getRuleCode());
String rid = "four:block:" + bid + ":rule:" + r.getRuleCode();
ruleNodeIds.add(rid);
String ruleLabel = ObjectUtil.defaultIfBlank(r.getRuleName(), r.getRuleCode());
Map<String, Object> rp = new LinkedHashMap<>();
rp.put("ruleCode", r.getRuleCode());
rp.put("ruleName", r.getRuleName());
rp.put("priorityNo", r.getPriorityNo());
rp.put("levelCode", r.getLevelCode());
rp.put("kindCode", r.getKindCode());
rp.put("moduleCode", r.getModuleCode());
if (ObjectUtil.isNotEmpty(r.getConditionExpr())) {
rp.put("conditionExpr", r.getConditionExpr());
}
if (ObjectUtil.isNotEmpty(r.getActionExpr())) {
rp.put("actionExpr", r.getActionExpr());
}
if (ObjectUtil.isNotEmpty(r.getRemark())) {
rp.put("remark", r.getRemark());
}
nodes.add(node(rid, ruleLabel, "rule_row", rp));
addEdge(edges, edgeIds, "four:drools_contains:" + bid + ":" + r.getRuleCode(),
droolsId, rid, "drools_contains", "包含");
}
for (int i = 0; i < ruleNodeIds.size() - 1; i++) {
addEdge(edges, edgeIds, "four:priority_next:" + bid + ":" + i,
ruleNodeIds.get(i), ruleNodeIds.get(i + 1), "rule_priority_next", "优先级顺序");
}
for (RuleConfig r : moduleRules) {
if (r == null || ObjectUtil.isEmpty(r.getRuleCode()) || !ruleCodeSet.contains(r.getRuleCode())) {
continue;
}
String rid = "four:block:" + bid + ":rule:" + r.getRuleCode();
List<String> outs = RuleFourBlockRuleOutputCatalog.outputsForRule(def.getModuleCode(), r.getRuleCode());
for (int j = 0; j < outs.size(); j++) {
String full = outs.get(j);
if (ObjectUtil.isEmpty(full)) {
continue;
}
String oid = "four:block:" + bid + ":out:" + r.getRuleCode() + ":" + j;
Map<String, Object> op = new LinkedHashMap<>();
op.put("detail", full);
op.put("ruleCode", r.getRuleCode());
op.put("outputIndex", j);
String lab = full.length() > 42 ? full.substring(0, 42) + "" : full;
nodes.add(node(oid, lab, "rule_output", op));
addEdge(edges, edgeIds, "four:produces:" + bid + ":" + r.getRuleCode() + ":" + j,
rid, oid, "rule_produces", "产出");
}
}
for (RuleConfigParam p : plist) {
if (p == null || ObjectUtil.isEmpty(p.getParamKey()) || ObjectUtil.isEmpty(p.getRuleCode())) {
continue;
}
if (p.getEnabled() != null && p.getEnabled() == 0) {
continue;
}
if (!ruleCodeSet.contains(p.getRuleCode())) {
continue;
}
String pk = p.getParamKey();
String pid = "four:block:" + bid + ":param:" + p.getRuleCode() + ":" + pk;
RuleParamMeta pm = resolveMeta(pk);
String pLabel = ObjectUtil.defaultIfBlank(p.getParamName(), pk);
Map<String, Object> pl = new LinkedHashMap<>();
pl.put("paramKey", pk);
pl.put("storedRaw", p.getParamVal());
pl.put("effectiveValue", globalPreview != null ? globalPreview.get(pk) : null);
pl.put("ruleCode", p.getRuleCode());
if (pm != null) {
pl.put("metaLabel", pm.getLabel());
pl.put("valueType", pm.getValueType());
pl.put("description", pm.getDescription());
}
nodes.add(node(pid, pLabel, "param", pl));
String rid = "four:block:" + bid + ":rule:" + p.getRuleCode();
addEdge(edges, edgeIds, "four:has_param:" + bid + ":" + p.getRuleCode() + ":" + pk,
rid, pid, "rule_has_param", "参数");
RuleConfig rr = ruleByCode.get(p.getRuleCode());
String fullWhen = formatParamWhenForRow(rr, def);
String cid = "four:block:" + bid + ":cond:" + p.getRuleCode() + ":" + pk;
String shortLab;
if (ObjectUtil.isEmpty(fullWhen)) {
shortLab = "(无条件)";
} else {
shortLab = fullWhen.length() > 36 ? fullWhen.substring(0, 36) + "" : fullWhen;
}
Map<String, Object> cp = new LinkedHashMap<>();
cp.put("fullWhenText", fullWhen);
cp.put("paramKey", pk);
cp.put("ruleCode", p.getRuleCode());
nodes.add(node(cid, shortLab, "param_condition", cp));
addEdge(edges, edgeIds, "four:cond_to_param:" + bid + ":" + p.getRuleCode() + ":" + pk,
cid, pid, "condition_applies", "条件");
}
graph.setLayoutHint("radial_hub");
graph.setFocusNodeId(droolsId);
return graph;
}
private List<RuleDictItem> safeDict(String dictType) {
List<RuleDictItem> list = ruleConfigMapper.selectDictByType(dictType);
return list != null ? list : Collections.emptyList();

View File

@@ -7,42 +7,73 @@
- that was distributed with this source code.
-->
<template>
<div class="rule-knowledge-graph">
<div
ref="graphShellRef"
class="rule-knowledge-graph"
:class="{
'rule-knowledge-graph--four-blocks': density === 'four-blocks',
'rule-knowledge-graph--fullscreen': isFullscreen,
}"
>
<div class="rule-knowledge-graph__toolbar">
<a-radio-group v-model:value="density" size="small" button-style="solid">
<a-radio-button value="overview">简要结构</a-radio-button>
<a-radio-button value="full">完整</a-radio-button>
<a-radio-button value="four-blocks">四块分区</a-radio-button>
</a-radio-group>
<span class="rule-knowledge-graph__hint">
简要仅层级种类模块规则完整含参数任务类型与执行顺序边
<span v-if="density !== 'four-blocks'" class="rule-knowledge-graph__hint">
简要仅层级种类模块规则完整含参数任务类型与执行顺序边四块业务运算步骤 + 规则项 + 参数 globalParams 一致
</span>
<span v-else class="rule-knowledge-graph__hint rule-knowledge-graph__hint--compact">
四宫格 · 拖拽画布 / 滚轮缩放
</span>
<a-button type="default" size="small" class="rule-knowledge-graph__fullscreen-btn" @click="toggleFullscreen">
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
{{ isFullscreen ? '退出全屏' : '全屏' }}
</a-button>
</div>
<div v-if="errorMsg" class="rule-knowledge-graph__banner rule-knowledge-graph__banner--error">
{{ errorMsg }}
</div>
<div v-else-if="emptyHint" class="rule-knowledge-graph__banner">
{{ emptyHint }}
</div>
<div v-show="!errorMsg && !emptyHint" ref="hostRef" class="rule-knowledge-graph__host" />
<RuleFourBlocksPanel v-if="density === 'four-blocks'" :refresh-key="refreshKey" />
<template v-else>
<div v-if="errorMsg" class="rule-knowledge-graph__banner rule-knowledge-graph__banner--error">
{{ errorMsg }}
</div>
<div v-else-if="emptyHint" class="rule-knowledge-graph__banner">
{{ emptyHint }}
</div>
<div v-show="!errorMsg && !emptyHint" ref="hostRef" class="rule-knowledge-graph__host" />
</template>
</div>
</template>
<script setup lang="ts">
import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
import { Graph } from '@antv/g6';
import { message } from 'ant-design-vue';
import { nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { findRuleConfigGraph } from './api';
import RuleFourBlocksPanel from './RuleFourBlocksPanel.vue';
import type { RuleConfigRequest, RuleGraphEdge, RuleGraphNode, RuleGraphPayload } from './types';
type RuleGraphDensityMode = 'overview' | 'full' | 'four-blocks';
const emit = defineEmits<{
densityChange: [RuleGraphDensityMode],
}>();
const props = defineProps<{
query: RuleConfigRequest,
refreshKey: number,
}>();
const graphShellRef = ref<HTMLElement | null>(null);
const hostRef = ref<HTMLDivElement | null>(null);
const isFullscreen = ref(false);
const errorMsg = ref<string | null>(null);
const emptyHint = ref<string | null>(null);
const density = ref<'overview' | 'full'>('overview');
const density = ref<RuleGraphDensityMode>('overview');
const lastPayload = ref<RuleGraphPayload | null>(null);
let graph: Graph | null = null;
@@ -127,6 +158,48 @@ const toGraphData = (payload: RuleGraphPayload) => ({
})),
});
const syncFullscreenState = () => {
const el = graphShellRef.value;
isFullscreen.value = Boolean(el && document.fullscreenElement === el);
};
const refitMainGraphAfterLayout = () => {
if (!graph || !hostRef.value) {
return;
}
const w = Math.max(hostRef.value.clientWidth, 280);
const h = Math.max(hostRef.value.clientHeight, 240);
graph.setSize(w, h);
void graph.fitView();
};
const onFullscreenChange = () => {
syncFullscreenState();
void nextTick(() => {
refitMainGraphAfterLayout();
window.dispatchEvent(new Event('resize'));
window.setTimeout(() => {
refitMainGraphAfterLayout();
}, 120);
});
};
const toggleFullscreen = async () => {
const el = graphShellRef.value;
if (!el) {
return;
}
try {
if (document.fullscreenElement === el) {
await document.exitFullscreen();
} else {
await el.requestFullscreen();
}
} catch {
message.warning('无法切换全屏,请检查浏览器权限或使用 Chrome / Edge');
}
};
const disposeGraph = async () => {
resizeObserver?.disconnect();
resizeObserver = null;
@@ -229,6 +302,9 @@ const buildGraph = async (payload: RuleGraphPayload, mode: 'overview' | 'full')
};
const renderFromCache = async () => {
if (density.value === 'four-blocks') {
return;
}
if (!lastPayload.value) {
return;
}
@@ -236,7 +312,8 @@ const renderFromCache = async () => {
return;
}
await nextTick();
await buildGraph(lastPayload.value, density.value);
const mode: 'overview' | 'full' = density.value === 'full' ? 'full' : 'overview';
await buildGraph(lastPayload.value, mode);
};
const load = async () => {
@@ -278,16 +355,34 @@ const load = async () => {
watch(
() => [props.query.pageNum, props.query.pageSize, props.refreshKey],
() => {
if (density.value === 'four-blocks') {
return;
}
void load();
},
{ immediate: true },
);
watch(density, () => {
void renderFromCache();
watch(density, async (mode, prev) => {
emit('densityChange', mode);
if (mode === 'four-blocks') {
await disposeGraph();
return;
}
if (prev === 'four-blocks') {
await load();
return;
}
await renderFromCache();
});
onMounted(() => {
syncFullscreenState();
document.addEventListener('fullscreenchange', onFullscreenChange);
});
onBeforeUnmount(() => {
document.removeEventListener('fullscreenchange', onFullscreenChange);
void disposeGraph();
});
</script>
@@ -311,6 +406,14 @@ onBeforeUnmount(() => {
padding: 0 2px 4px;
}
.rule-knowledge-graph__fullscreen-btn {
margin-left: auto;
flex-shrink: 0;
color: #b8ccd6;
border-color: rgba(120, 170, 200, 0.45);
background: rgba(10, 28, 40, 0.65);
}
.rule-knowledge-graph__hint {
font-size: 11px;
color: #7a8d96;
@@ -318,6 +421,35 @@ onBeforeUnmount(() => {
max-width: 100%;
}
.rule-knowledge-graph__hint--compact {
font-size: 10px;
color: #6d8290;
}
.rule-knowledge-graph--four-blocks {
min-height: 0;
}
.rule-knowledge-graph--fullscreen {
box-sizing: border-box;
width: 100vw;
height: 100vh;
max-height: 100vh;
padding: 10px 12px;
gap: 10px;
background: #0d1f2c;
}
.rule-knowledge-graph--fullscreen .rule-knowledge-graph__host {
min-height: 0;
flex: 1;
}
.rule-knowledge-graph--fullscreen :deep(.rule-four-blocks) {
flex: 1;
min-height: 0;
}
.rule-knowledge-graph__host {
flex: 1;
min-height: 200px;

View File

@@ -9,7 +9,7 @@
import { HttpRequestClient } from '@/utils/request';
import type { ApiDataResponse, BasicResponse } from '@/types';
import type { RuleConfig, RuleConfigPageableResponse, RuleConfigRequest, RuleDictItem, RuleGraphPayload, RuleParamMeta } from './types';
import type { RuleConfig, RuleConfigPageableResponse, RuleConfigRequest, RuleDictItem, RuleFourBlocksPayload, RuleGraphPayload, RuleParamMeta } from './types';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api',
@@ -46,3 +46,7 @@ export const findRuleParamMeta = (): Promise<ApiDataResponse<RuleParamMeta[]>> =
export const findRuleConfigGraph = (query: Partial<RuleConfigRequest> = {}): Promise<ApiDataResponse<RuleGraphPayload>> => {
return req.get('/system/rule/config/graph', query);
};
export const findRuleFourBlocksGraph = (): Promise<ApiDataResponse<RuleFourBlocksPayload>> => {
return req.get('/system/rule/config/graph/four-blocks');
};

View File

@@ -52,7 +52,7 @@
maxWidth: '78%',
}"
>
<RuleKnowledgeGraph :query="query" :refresh-key="graphRevision" />
<RuleKnowledgeGraph :query="query" :refresh-key="graphRevision" @density-change="onRuleGraphDensityChange" />
</div>
<div
class="rule-config-main-split-resizer"
@@ -251,12 +251,27 @@ const GRAPH_PANE_STORAGE_KEY = 'rule-config-graph-pane-percent';
const GRAPH_PANE_MIN = 18;
const GRAPH_PANE_MAX = 70;
const graphPanePercent = ref(32);
const graphPanePercentBeforeFourBlocks = ref<number | null>(null);
const splitRootRef = ref<HTMLElement | null>(null);
const graphRevision = ref(0);
const clampGraphPercent = (n: number) =>
Math.min(GRAPH_PANE_MAX, Math.max(GRAPH_PANE_MIN, Math.round(n)));
const onRuleGraphDensityChange = (mode: 'overview' | 'full' | 'four-blocks') => {
if (mode === 'four-blocks') {
if (graphPanePercentBeforeFourBlocks.value === null) {
graphPanePercentBeforeFourBlocks.value = graphPanePercent.value;
}
graphPanePercent.value = clampGraphPercent(62);
return;
}
if (graphPanePercentBeforeFourBlocks.value !== null) {
graphPanePercent.value = clampGraphPercent(graphPanePercentBeforeFourBlocks.value);
graphPanePercentBeforeFourBlocks.value = null;
}
};
const onGraphSplitMouseDown = (e: MouseEvent) => {
e.preventDefault();
const root = splitRootRef.value;

View File

@@ -105,4 +105,33 @@ export interface RuleGraphEdge {
export interface RuleGraphPayload {
nodes: RuleGraphNode[],
edges: RuleGraphEdge[],
layoutHint?: string | null,
focusNodeId?: string | null,
}
export interface RuleFourBlockParamRow {
ruleCode: NullableString,
ruleName: NullableString,
paramKey: NullableString,
paramName: NullableString,
whenText: NullableString,
thenText: NullableString,
outputText: NullableString,
}
export interface RuleFourBlockCluster {
blockId: string,
moduleCode: NullableString,
title: NullableString,
droolsRuleName: NullableString,
salience: number | null,
whenExpr: NullableString,
thenAction: NullableString,
graph: RuleGraphPayload,
paramRows?: RuleFourBlockParamRow[] | null,
}
export interface RuleFourBlocksPayload {
globalParamsPreview: Record<string, unknown>,
blocks: RuleFourBlockCluster[],
}