2026-04-17 14:52:10 +08:00
|
|
|
|
<!--
|
|
|
|
|
|
- This file is part of the kernelstudio package.
|
|
|
|
|
|
-
|
|
|
|
|
|
- (c) 2014-2026 zlin <admin@kernelstudio.com>
|
|
|
|
|
|
-
|
|
|
|
|
|
- For the full copyright and license information, please view the LICENSE file
|
|
|
|
|
|
- that was distributed with this source code.
|
|
|
|
|
|
-->
|
|
|
|
|
|
<template>
|
2026-04-20 09:38:58 +08:00
|
|
|
|
<div
|
|
|
|
|
|
ref="graphShellRef"
|
|
|
|
|
|
class="rule-knowledge-graph"
|
|
|
|
|
|
:class="{
|
|
|
|
|
|
'rule-knowledge-graph--four-blocks': density === 'four-blocks',
|
|
|
|
|
|
'rule-knowledge-graph--fullscreen': isFullscreen,
|
|
|
|
|
|
}"
|
|
|
|
|
|
>
|
2026-04-17 14:52:10 +08:00
|
|
|
|
<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>
|
2026-04-20 09:38:58 +08:00
|
|
|
|
<a-radio-button value="four-blocks">四块分区</a-radio-button>
|
2026-04-17 14:52:10 +08:00
|
|
|
|
</a-radio-group>
|
2026-04-20 09:38:58 +08:00
|
|
|
|
<span v-if="density !== 'four-blocks'" class="rule-knowledge-graph__hint">
|
|
|
|
|
|
简要:仅层级→种类→模块→规则;完整:含参数、任务类型与执行顺序边;四块:业务运算步骤 + 规则项 + 参数(与 globalParams 一致)
|
2026-04-17 14:52:10 +08:00
|
|
|
|
</span>
|
2026-04-20 09:38:58 +08:00
|
|
|
|
<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>
|
2026-04-17 14:52:10 +08:00
|
|
|
|
</div>
|
2026-04-20 09:38:58 +08:00
|
|
|
|
<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>
|
2026-04-17 14:52:10 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-04-20 09:38:58 +08:00
|
|
|
|
import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
|
2026-04-17 14:52:10 +08:00
|
|
|
|
import { Graph } from '@antv/g6';
|
|
|
|
|
|
import { message } from 'ant-design-vue';
|
2026-04-20 09:38:58 +08:00
|
|
|
|
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
2026-04-17 14:52:10 +08:00
|
|
|
|
import { findRuleConfigGraph } from './api';
|
2026-04-20 09:38:58 +08:00
|
|
|
|
import RuleFourBlocksPanel from './RuleFourBlocksPanel.vue';
|
2026-04-17 14:52:10 +08:00
|
|
|
|
import type { RuleConfigRequest, RuleGraphEdge, RuleGraphNode, RuleGraphPayload } from './types';
|
|
|
|
|
|
|
2026-04-20 09:38:58 +08:00
|
|
|
|
type RuleGraphDensityMode = 'overview' | 'full' | 'four-blocks';
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
densityChange: [RuleGraphDensityMode],
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
2026-04-17 14:52:10 +08:00
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
query: RuleConfigRequest,
|
|
|
|
|
|
refreshKey: number,
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
2026-04-20 09:38:58 +08:00
|
|
|
|
const graphShellRef = ref<HTMLElement | null>(null);
|
2026-04-17 14:52:10 +08:00
|
|
|
|
const hostRef = ref<HTMLDivElement | null>(null);
|
2026-04-20 09:38:58 +08:00
|
|
|
|
const isFullscreen = ref(false);
|
2026-04-17 14:52:10 +08:00
|
|
|
|
const errorMsg = ref<string | null>(null);
|
|
|
|
|
|
const emptyHint = ref<string | null>(null);
|
2026-04-20 09:38:58 +08:00
|
|
|
|
const density = ref<RuleGraphDensityMode>('overview');
|
2026-04-17 14:52:10 +08:00
|
|
|
|
const lastPayload = ref<RuleGraphPayload | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
let graph: Graph | null = null;
|
|
|
|
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
const NODE_COLORS: Record<string, string> = {
|
|
|
|
|
|
level: '#5B8FF9',
|
|
|
|
|
|
kind: '#61DDAA',
|
|
|
|
|
|
module: '#65789B',
|
|
|
|
|
|
rule: '#F6903D',
|
|
|
|
|
|
param: '#9270CA',
|
|
|
|
|
|
taskType: '#269A99',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const str = (v: unknown): string => (v === null || v === undefined ? '' : String(v));
|
|
|
|
|
|
|
|
|
|
|
|
/** 简要:只保留结构主干,边改为 level → kind → module → rule 便于分层布局 */
|
|
|
|
|
|
const buildOverviewPayload = (payload: RuleGraphPayload): RuleGraphPayload => {
|
|
|
|
|
|
const keepTypes = new Set(['level', 'kind', 'module', 'rule']);
|
|
|
|
|
|
const nodes = payload.nodes.filter((n) => keepTypes.has(n.nodeType));
|
|
|
|
|
|
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
|
|
|
|
|
|
|
|
|
|
const edgeSeen = new Set<string>();
|
|
|
|
|
|
const edges: RuleGraphEdge[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
const addEdge = (source: string, target: string, suffix: string) => {
|
|
|
|
|
|
if (!nodeIds.has(source) || !nodeIds.has(target)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const key = `${source}|${target}`;
|
|
|
|
|
|
if (edgeSeen.has(key)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
edgeSeen.add(key);
|
|
|
|
|
|
edges.push({
|
|
|
|
|
|
id: `ov:${suffix}:${key}`,
|
|
|
|
|
|
source,
|
|
|
|
|
|
target,
|
|
|
|
|
|
edgeType: 'overview_hierarchy',
|
|
|
|
|
|
label: null,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for (const r of nodes) {
|
|
|
|
|
|
if (r.nodeType !== 'rule' || !r.payload) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
const levelCode = str(r.payload.levelCode);
|
|
|
|
|
|
const kindCode = str(r.payload.kindCode);
|
|
|
|
|
|
const moduleCode = str(r.payload.moduleCode);
|
|
|
|
|
|
if (!levelCode || !kindCode || !moduleCode) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
const lid = `level:${levelCode}`;
|
|
|
|
|
|
const kid = `kind:${levelCode}:${kindCode}`;
|
|
|
|
|
|
const mid = `module:${moduleCode}`;
|
|
|
|
|
|
addEdge(lid, kid, 'lk');
|
|
|
|
|
|
addEdge(kid, mid, 'km');
|
|
|
|
|
|
addEdge(mid, r.id, 'mr');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { nodes, edges };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const toGraphData = (payload: RuleGraphPayload) => ({
|
|
|
|
|
|
nodes: payload.nodes.map((n) => ({
|
|
|
|
|
|
id: n.id,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
label: n.label,
|
|
|
|
|
|
nodeType: n.nodeType,
|
|
|
|
|
|
payload: n.payload ?? {},
|
|
|
|
|
|
},
|
|
|
|
|
|
})),
|
|
|
|
|
|
edges: payload.edges.map((e) => ({
|
|
|
|
|
|
id: e.id,
|
|
|
|
|
|
source: e.source,
|
|
|
|
|
|
target: e.target,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
edgeType: e.edgeType ?? '',
|
|
|
|
|
|
label: e.label ?? '',
|
|
|
|
|
|
},
|
|
|
|
|
|
})),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-20 09:38:58 +08:00
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-17 14:52:10 +08:00
|
|
|
|
const disposeGraph = async () => {
|
|
|
|
|
|
resizeObserver?.disconnect();
|
|
|
|
|
|
resizeObserver = null;
|
|
|
|
|
|
if (graph) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
graph.destroy();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
/* ignore */
|
|
|
|
|
|
}
|
|
|
|
|
|
graph = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const buildGraph = async (payload: RuleGraphPayload, mode: 'overview' | 'full') => {
|
|
|
|
|
|
await disposeGraph();
|
|
|
|
|
|
const el = hostRef.value;
|
|
|
|
|
|
if (!el) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const raw = mode === 'overview' ? buildOverviewPayload(payload) : payload;
|
|
|
|
|
|
const data = toGraphData(raw);
|
|
|
|
|
|
if (raw.nodes.length === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const width = Math.max(el.clientWidth, 280);
|
|
|
|
|
|
const height = Math.max(el.clientHeight, 240);
|
|
|
|
|
|
|
|
|
|
|
|
const layout =
|
|
|
|
|
|
mode === 'overview'
|
|
|
|
|
|
? {
|
|
|
|
|
|
type: 'antv-dagre' as const,
|
|
|
|
|
|
rankdir: 'TB',
|
|
|
|
|
|
ranksep: 56,
|
|
|
|
|
|
nodesep: 36,
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
type: 'd3-force' as const,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
graph = new Graph({
|
|
|
|
|
|
container: el,
|
|
|
|
|
|
width,
|
|
|
|
|
|
height,
|
|
|
|
|
|
data,
|
|
|
|
|
|
layout,
|
|
|
|
|
|
node: {
|
|
|
|
|
|
style: {
|
|
|
|
|
|
size: (d: { data?: { nodeType?: string } }) => {
|
|
|
|
|
|
const t = d.data?.nodeType;
|
|
|
|
|
|
if (mode === 'overview') {
|
|
|
|
|
|
if (t === 'rule') return 20;
|
|
|
|
|
|
if (t === 'module') return 18;
|
|
|
|
|
|
return 16;
|
|
|
|
|
|
}
|
|
|
|
|
|
return t === 'param' ? 5 : 11;
|
|
|
|
|
|
},
|
|
|
|
|
|
fill: (d: { data?: { nodeType?: string } }) => NODE_COLORS[d.data?.nodeType ?? ''] ?? '#8B8B8B',
|
|
|
|
|
|
labelText: (d: { data?: { label?: string }; id: string }) => d.data?.label ?? String(d.id),
|
|
|
|
|
|
labelFill: '#e8f4f8',
|
|
|
|
|
|
labelFontSize: (d: { data?: { nodeType?: string } }) =>
|
|
|
|
|
|
(mode === 'overview' ? (d.data?.nodeType === 'rule' ? 11 : 12) : 9),
|
|
|
|
|
|
labelMaxWidth: mode === 'overview' ? 200 : 100,
|
|
|
|
|
|
labelWordWrap: true,
|
|
|
|
|
|
lineWidth: 1.5,
|
|
|
|
|
|
stroke: '#0d1f2c',
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
edge: {
|
|
|
|
|
|
style: {
|
|
|
|
|
|
stroke: (d: { data?: { edgeType?: string } }) => {
|
|
|
|
|
|
const t = d.data?.edgeType;
|
|
|
|
|
|
if (t === 'overview_hierarchy') {
|
|
|
|
|
|
return 'rgba(120, 170, 200, 0.55)';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (t === 'rule_exec_before') {
|
|
|
|
|
|
return '#faad14';
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'rgba(150, 175, 190, 0.35)';
|
|
|
|
|
|
},
|
|
|
|
|
|
lineWidth: (d: { data?: { edgeType?: string } }) =>
|
|
|
|
|
|
(d.data?.edgeType === 'rule_exec_before' ? 2 : 1),
|
|
|
|
|
|
endArrow: mode !== 'overview',
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
|
|
|
|
|
|
});
|
|
|
|
|
|
await graph.render();
|
|
|
|
|
|
await graph.fitView();
|
|
|
|
|
|
resizeObserver = new ResizeObserver(() => {
|
|
|
|
|
|
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();
|
|
|
|
|
|
});
|
|
|
|
|
|
resizeObserver.observe(el);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const renderFromCache = async () => {
|
2026-04-20 09:38:58 +08:00
|
|
|
|
if (density.value === 'four-blocks') {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-17 14:52:10 +08:00
|
|
|
|
if (!lastPayload.value) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!lastPayload.value.nodes?.length) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
await nextTick();
|
2026-04-20 09:38:58 +08:00
|
|
|
|
const mode: 'overview' | 'full' = density.value === 'full' ? 'full' : 'overview';
|
|
|
|
|
|
await buildGraph(lastPayload.value, mode);
|
2026-04-17 14:52:10 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const load = async () => {
|
|
|
|
|
|
errorMsg.value = null;
|
|
|
|
|
|
emptyHint.value = null;
|
|
|
|
|
|
await disposeGraph();
|
|
|
|
|
|
try {
|
|
|
|
|
|
const r = await findRuleConfigGraph({
|
|
|
|
|
|
pageNum: props.query.pageNum,
|
|
|
|
|
|
pageSize: props.query.pageSize,
|
|
|
|
|
|
ruleCode: props.query.ruleCode ?? undefined,
|
|
|
|
|
|
ruleName: props.query.ruleName ?? undefined,
|
|
|
|
|
|
levelCode: props.query.levelCode ?? undefined,
|
|
|
|
|
|
kindCode: props.query.kindCode ?? undefined,
|
|
|
|
|
|
moduleCode: props.query.moduleCode ?? undefined,
|
|
|
|
|
|
enabled: props.query.enabled ?? undefined,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (r.code !== 200 || !r.data) {
|
|
|
|
|
|
errorMsg.value = r.msg ?? '加载知识图谱失败';
|
|
|
|
|
|
lastPayload.value = null;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const payload = r.data;
|
|
|
|
|
|
if (!payload.nodes?.length) {
|
|
|
|
|
|
emptyHint.value = '当前页无规则数据,图谱为空';
|
|
|
|
|
|
lastPayload.value = null;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
lastPayload.value = payload;
|
|
|
|
|
|
await renderFromCache();
|
|
|
|
|
|
} catch (e: unknown) {
|
|
|
|
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
|
|
|
|
errorMsg.value = `图谱请求失败:${msg}`;
|
|
|
|
|
|
lastPayload.value = null;
|
|
|
|
|
|
message.error(errorMsg.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => [props.query.pageNum, props.query.pageSize, props.refreshKey],
|
|
|
|
|
|
() => {
|
2026-04-20 09:38:58 +08:00
|
|
|
|
if (density.value === 'four-blocks') {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-17 14:52:10 +08:00
|
|
|
|
void load();
|
|
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-04-20 09:38:58 +08:00
|
|
|
|
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);
|
2026-04-17 14:52:10 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
2026-04-20 09:38:58 +08:00
|
|
|
|
document.removeEventListener('fullscreenchange', onFullscreenChange);
|
2026-04-17 14:52:10 +08:00
|
|
|
|
void disposeGraph();
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
.rule-knowledge-graph {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.rule-knowledge-graph__toolbar {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding: 0 2px 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:38:58 +08:00
|
|
|
|
.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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 14:52:10 +08:00
|
|
|
|
.rule-knowledge-graph__hint {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #7a8d96;
|
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:38:58 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-17 14:52:10 +08:00
|
|
|
|
.rule-knowledge-graph__host {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.rule-knowledge-graph__banner {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #a2b1ba;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.rule-knowledge-graph__banner--error {
|
|
|
|
|
|
color: #ff9c9c;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|