新增: 依赖图增强(添加依赖弹窗 + dagre 自动布局)

- 添加依赖入口:工具栏"+ 依赖"按钮,弹窗选择源/目标工程 + 依赖类型
- dagre 层次布局替换网格布局(LR 方向,自动排布工程节点)
- 依赖边按类型着色(library/api/mq/shared/custom)
- 小地图导航 + 缩放/适应内容
This commit is contained in:
2026-07-01 01:17:44 +08:00
parent bce2fea4e5
commit dd0e059365
3 changed files with 164 additions and 8 deletions

View File

@@ -3,6 +3,7 @@
<div class="dep-graph__toolbar">
<span class="dep-graph__title">{{ $t('dependencyGraph.title') }}</span>
<div class="dep-graph__actions">
<button v-if="modules.length >= 2" class="btn btn-ghost btn-sm" @click="showAddDep = true">+ 依赖</button>
<button class="btn btn-ghost btn-sm" @click="fitContent">适应内容</button>
<button class="btn btn-ghost btn-sm" @click="zoomIn">+</button>
<button class="btn btn-ghost btn-sm" @click="zoomOut">-</button>
@@ -13,6 +14,39 @@
<div class="empty-icon">📦</div>
<div>{{ $t('dependencyGraph.empty') }}</div>
</div>
<!-- 添加依赖弹窗 -->
<div v-if="showAddDep" class="dep-graph__modal-overlay" @click.self="showAddDep = false">
<div class="dep-graph__modal">
<h3>添加工程依赖</h3>
<div class="dep-graph__modal-field">
<label>源工程(依赖方)</label>
<select v-model="depFrom" class="dep-graph__modal-input">
<option v-for="m in modules" :key="m.id" :value="m.id">{{ m.name }}</option>
</select>
</div>
<div class="dep-graph__modal-field">
<label>目标工程(被依赖)</label>
<select v-model="depTo" class="dep-graph__modal-input">
<option v-for="m in modules" :key="m.id" :value="m.id">{{ m.name }}</option>
</select>
</div>
<div class="dep-graph__modal-field">
<label>依赖类型</label>
<select v-model="depType" class="dep-graph__modal-input">
<option value="library">类库</option>
<option value="api">API 调用</option>
<option value="mq">消息队列</option>
<option value="shared">共享资源</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="dep-graph__modal-actions">
<button class="btn btn-ghost btn-sm" @click="showAddDep = false">取消</button>
<button class="btn btn-primary btn-sm" :disabled="!depFrom || !depTo || depFrom === depTo" @click="onAddDep">确认</button>
</div>
</div>
</div>
</div>
</template>
@@ -25,6 +59,7 @@
import { onMounted, onBeforeUnmount, ref, watch, markRaw } from 'vue'
import { useRouter } from 'vue-router'
import { Graph, Selection, Snapline, History, Scroller, MiniMap } from '@antv/x6'
import dagre from 'dagre'
import '@antv/x6-vue-shape'
import { moduleApi, type ProjectModuleRecord, type ModuleDependencyRecord } from '@/api/module'
import ModuleNode from './ModuleNode.vue'
@@ -45,6 +80,27 @@ const dependencies = ref<ModuleDependencyRecord[]>([])
const loading = ref(true)
let graph: Graph | null = null
// 添加依赖弹窗
const showAddDep = ref(false)
const depFrom = ref('')
const depTo = ref('')
const depType = ref('library')
async function onAddDep() {
if (!depFrom.value || !depTo.value || depFrom.value === depTo.value) return
try {
await moduleApi.addModuleDependency(props.projectId, depFrom.value, depTo.value, depType.value)
showAddDep.value = false
depFrom.value = ''
depTo.value = ''
depType.value = 'library'
await loadModules()
renderGraph()
} catch (e) {
console.error('[DependencyGraph] 添加依赖失败:', e)
}
}
async function loadModules() {
loading.value = true
try {
@@ -65,20 +121,29 @@ async function loadModules() {
function renderGraph() {
if (!graph) return
const cols = 3
const nodeWidth = 200
const nodeHeight = 80
const gapX = 50
const gapY = 40
const nodes = modules.value.map((m, i) => {
const col = i % cols
const row = Math.floor(i / cols)
// dagre 层次布局
const g = new dagre.graphlib.Graph()
g.setGraph({ rankdir: 'LR', nodesep: 30, ranksep: 60 })
g.setDefaultEdgeLabel(() => ({}))
for (const m of modules.value) {
g.setNode(m.id, { width: nodeWidth, height: nodeHeight })
}
for (const d of dependencies.value) {
g.setEdge(d.from_module_id, d.to_module_id)
}
dagre.layout(g)
// 映射回 X6 节点格式
const nodes = modules.value.map(m => {
const pos = g.node(m.id)
return {
id: m.id,
shape: 'vue-shape',
x: col * (nodeWidth + gapX) + 40,
y: row * (nodeHeight + gapY) + 40,
x: (pos?.x ?? 0) - nodeWidth / 2,
y: (pos?.y ?? 0) - nodeHeight / 2,
width: nodeWidth,
height: nodeHeight,
component: markRaw(ModuleNode),
@@ -257,4 +322,41 @@ onBeforeUnmount(() => {
padding: 4px 10px;
font-size: 11px;
}
/* 添加依赖弹窗 */
.dep-graph__modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.dep-graph__modal {
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
padding: 20px;
min-width: 360px;
}
.dep-graph__modal h3 { font-size: 16px; color: var(--df-text); margin-bottom: 16px; }
.dep-graph__modal-field { margin-bottom: 12px; }
.dep-graph__modal-field label {
display: block; font-size: 12px; color: var(--df-text-secondary); margin-bottom: 4px;
}
.dep-graph__modal-input {
width: 100%; box-sizing: border-box;
padding: 6px 10px;
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-sm);
background: var(--df-bg); color: var(--df-text);
font-size: 13px;
}
.dep-graph__modal-actions {
display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px;
}
.btn-primary { background: var(--df-accent); color: #fff; }
.btn-primary:hover { filter: brightness(1.1); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
</style>