新增: 工程依赖图数据层 + 依赖边渲染 + 小地图

- 后端 module_dependencies 表(V35 迁移)+ ModuleDependencyRepo CRUD
- IPC:add/remove/list_module_dependencies
- 前端 API 封装 + DependencyGraph 接入真实边数据
- 依赖类型颜色区分(library/api/mq/shared/custom)
- 小地图插件(MiniMap)大图概览导航
- 点击节点跳转项目详情
- 任务列表后端真分页 count_by_query 方法
This commit is contained in:
2026-07-01 00:20:51 +08:00
parent 3c2fa91fc6
commit 3758bea0b5
10 changed files with 316 additions and 10 deletions

View File

@@ -59,6 +59,17 @@ export interface GitChangedFile {
path: string
}
/** 工程依赖关系记录(对齐后端 ModuleDependencyRecord)。 */
export interface ModuleDependencyRecord {
id: string
project_id: string
from_module_id: string
to_module_id: string
dep_type: string
label: string | null
created_at: string
}
/** 最近提交项(对齐后端 GitRecentCommit)。 */
export interface GitRecentCommit {
hash: string
@@ -157,6 +168,21 @@ export const moduleApi = {
return invoke('list_branches', { moduleId })
},
/** 列出项目全部工程依赖。 */
listModuleDependencies(projectId: string): Promise<ModuleDependencyRecord[]> {
return invoke('list_module_dependencies', { projectId })
},
/** 新增工程依赖。 */
addModuleDependency(projectId: string, fromModuleId: string, toModuleId: string, depType?: string, label?: string): Promise<ModuleDependencyRecord> {
return invoke('add_module_dependency', { projectId, fromModuleId, toModuleId, depType: depType ?? null, label: label ?? null })
},
/** 删除工程依赖。 */
removeModuleDependency(id: string): Promise<void> {
return invoke('remove_module_dependency', { id })
},
getModuleCommits(moduleId: string, skip?: number, limit?: number): Promise<{
commits: { hash: string; subject: string; timestamp: number; author: string }[]
has_more: boolean

View File

@@ -23,30 +23,40 @@
* 节点用 Vue 组件(ModuleNode.vue)渲染,通过 @antv/x6-vue-shape 注册。
*/
import { onMounted, onBeforeUnmount, ref, watch, markRaw } from 'vue'
import { Graph, Selection, Snapline, History, Scroller } from '@antv/x6'
import { useRouter } from 'vue-router'
import { Graph, Selection, Snapline, History, Scroller, MiniMap } from '@antv/x6'
import '@antv/x6-vue-shape'
import { moduleApi, type ProjectModuleRecord } from '@/api/module'
import { moduleApi, type ProjectModuleRecord, type ModuleDependencyRecord } from '@/api/module'
import ModuleNode from './ModuleNode.vue'
const props = defineProps<{
projectId: string
}>()
const router = useRouter()
const emit = defineEmits<{
(e: 'select-module', moduleId: string): void
}>()
const containerRef = ref<HTMLElement | null>(null)
const modules = ref<ProjectModuleRecord[]>([])
const dependencies = ref<ModuleDependencyRecord[]>([])
const loading = ref(true)
let graph: Graph | null = null
async function loadModules() {
loading.value = true
try {
modules.value = await moduleApi.listProjectModules(props.projectId)
const [mods, deps] = await Promise.all([
moduleApi.listProjectModules(props.projectId),
moduleApi.listModuleDependencies(props.projectId),
])
modules.value = mods
dependencies.value = deps
} catch {
modules.value = []
dependencies.value = []
} finally {
loading.value = false
}
@@ -81,8 +91,21 @@ function renderGraph() {
}
})
// 边暂为空(module_dependencies 表未建)
graph.fromJSON({ nodes, edges: [] })
// 边数据(module_dependencies 表)
const edges = dependencies.value.map(d => ({
source: d.from_module_id,
target: d.to_module_id,
labels: d.label ? [{ text: d.label }] : [{ text: d.dep_type }],
attrs: {
line: {
stroke: depTypeColor(d.dep_type),
strokeWidth: 1.5,
targetMarker: { name: 'block', width: 8, height: 6 },
},
},
}))
graph.fromJSON({ nodes, edges })
graph.centerContent()
}
@@ -101,9 +124,11 @@ function buildGraph() {
graph.use(new Snapline({ enabled: true }))
graph.use(new History({ enabled: true }))
graph.use(new Scroller({ enabled: true, pannable: true }))
graph.use(new MiniMap({ width: 200, height: 120, padding: 10 }))
graph.on('node:click', ({ node }) => {
emit('select-module', String(node.id))
router.push(`/projects/${props.projectId}`)
})
renderGraph()
@@ -121,6 +146,17 @@ function zoomOut() {
graph?.zoom(-0.1)
}
/** 依赖类型 → 边颜色 */
function depTypeColor(depType: string): string {
switch (depType) {
case 'api': return '#5378e8'
case 'mq': return '#e8a053'
case 'shared': return '#53e8a0'
case 'custom': return '#e853a0'
default: return '#5c6b8a' // library
}
}
onMounted(async () => {
await loadModules()
buildGraph()