优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)
- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod - AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation - 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n - 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
@@ -17,6 +17,22 @@
|
||||
<div>{{ $t('dependencyGraph.empty') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 删除依赖确认弹层 -->
|
||||
<ConfirmDialog :visible="confirmState.visible" :msg="confirmState.msg" :danger-label="confirmState.dangerLabel" @result="answerConfirm" />
|
||||
|
||||
<!-- 边删除按钮(HTML 遮罩层):edge hover 时定位到边中点显示,leave 时隐藏。
|
||||
用 HTML 而非 X6 port 方案,因为 graph.fromJSON 重建所有 edge 会清掉动态加的 port。 -->
|
||||
<button
|
||||
v-if="deleteDepBtnVisible"
|
||||
class="dep-edge-delete-btn"
|
||||
:style="deleteDepBtnStyle"
|
||||
@click="onDeleteDepBtnClick"
|
||||
type="button"
|
||||
:title="$t('fileExplorer.deleteCurrentModule')"
|
||||
>
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
|
||||
<!-- 添加依赖弹窗 -->
|
||||
<div v-if="showAddDep" class="dep-graph__modal-overlay" @click.self="showAddDep = false">
|
||||
<div class="dep-graph__modal">
|
||||
@@ -60,12 +76,15 @@
|
||||
*/
|
||||
import { onMounted, onBeforeUnmount, ref, watch, markRaw } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Graph, Selection, Snapline, History, Scroller, MiniMap } from '@antv/x6'
|
||||
import dagre from 'dagre'
|
||||
import '@antv/x6-vue-shape'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { moduleApi, type ProjectModuleRecord, type ModuleDependencyRecord } from '@/api/module'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import ModuleNode from './ModuleNode.vue'
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
projectId: string
|
||||
@@ -81,8 +100,15 @@ const containerRef = ref<HTMLElement | null>(null)
|
||||
const modules = ref<ProjectModuleRecord[]>([])
|
||||
const dependencies = ref<ModuleDependencyRecord[]>([])
|
||||
const loading = ref(true)
|
||||
const { t } = useI18n()
|
||||
const { confirmDialog, confirmState, answerConfirm } = useConfirm()
|
||||
let graph: Graph | null = null
|
||||
|
||||
// 边删除按钮(HTML 遮罩层)
|
||||
const deleteDepBtnVisible = ref(false)
|
||||
const deleteDepBtnStyle = ref<{ left: string; top: string }>({ left: '0px', top: '0px' })
|
||||
let deleteDepTarget: ModuleDependencyRecord | null = null
|
||||
|
||||
// 添加依赖弹窗
|
||||
const showAddDep = ref(false)
|
||||
const depFrom = ref('')
|
||||
@@ -165,8 +191,9 @@ function renderGraph() {
|
||||
}
|
||||
})
|
||||
|
||||
// 边数据(module_dependencies 表)
|
||||
// 边数据(module_dependencies 表);edge id 用 dep.id,删除时按 id 反查源/目标
|
||||
const edges = dependencies.value.map(d => ({
|
||||
id: d.id,
|
||||
source: d.from_module_id,
|
||||
target: d.to_module_id,
|
||||
labels: d.label ? [{ text: d.label }] : [{ text: d.dep_type }],
|
||||
@@ -206,6 +233,7 @@ function buildGraph() {
|
||||
})
|
||||
|
||||
renderGraph()
|
||||
bindEdgeDeleteButtons()
|
||||
}
|
||||
|
||||
function fitContent() {
|
||||
@@ -254,6 +282,65 @@ async function exportPNG() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除依赖(二次确认 + 重绘)。 */
|
||||
async function confirmRemoveDep(dep: ModuleDependencyRecord) {
|
||||
const fromName = modules.value.find(m => m.id === dep.from_module_id)?.name ?? dep.from_module_id
|
||||
const toName = modules.value.find(m => m.id === dep.to_module_id)?.name ?? dep.to_module_id
|
||||
const ok = await confirmDialog(`确定删除「${fromName} → ${toName}」的依赖吗?此操作不可撤销。`, t('common.delete'))
|
||||
if (!ok) return
|
||||
try {
|
||||
await moduleApi.removeModuleDependency(dep.id)
|
||||
Message.info('依赖已删除')
|
||||
await loadModules()
|
||||
renderGraph()
|
||||
bindEdgeDeleteButtons()
|
||||
} catch (e) {
|
||||
console.error('[DependencyGraph] 删除依赖失败:', e)
|
||||
Message.error('删除依赖失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击遮罩层上的删除按钮触发。 */
|
||||
function onDeleteDepBtnClick() {
|
||||
const dep = deleteDepTarget
|
||||
if (dep) void confirmRemoveDep(dep)
|
||||
hideEdgeDeleteBtn()
|
||||
}
|
||||
|
||||
/** edge hover 时显示删除按钮(定位到边中点屏幕坐标)。 */
|
||||
function showEdgeDeleteBtn(edge: any) {
|
||||
const depId = String(edge.id)
|
||||
const dep = dependencies.value.find(d => d.id === depId)
|
||||
if (!dep || !containerRef.value || !graph) return
|
||||
deleteDepTarget = dep
|
||||
// 算边中点(view 坐标)→ 容器像素坐标:transform.scale + translate
|
||||
const bbox = edge.getBBox()
|
||||
const mx = bbox.x + bbox.width / 2
|
||||
const my = bbox.y + bbox.height / 2
|
||||
const ts = graph.translate()
|
||||
const sc = graph.scale()
|
||||
const viewX = mx * sc.sx + ts.tx
|
||||
const viewY = my * sc.sy + ts.ty
|
||||
deleteDepBtnStyle.value = { left: `${viewX - 8}px`, top: `${viewY - 8}px` }
|
||||
deleteDepBtnVisible.value = true
|
||||
}
|
||||
|
||||
/** edge leave 时隐藏删除按钮。 */
|
||||
function hideEdgeDeleteBtn() {
|
||||
deleteDepBtnVisible.value = false
|
||||
deleteDepTarget = null
|
||||
}
|
||||
|
||||
/** 给所有 edge 绑 hover 显示删除按钮的监听。 */
|
||||
function bindEdgeDeleteButtons() {
|
||||
if (!graph) return
|
||||
try { graph.off('edge:mouseenter', onEdgeEnter); graph.off('edge:mouseleave', onEdgeLeave) } catch { /* ignore */ }
|
||||
function onEdgeEnter(e: any) { showEdgeDeleteBtn(e.edge) }
|
||||
function onEdgeLeave() { hideEdgeDeleteBtn() }
|
||||
graph.on('edge:mouseenter', onEdgeEnter)
|
||||
graph.on('edge:mouseleave', onEdgeLeave)
|
||||
}
|
||||
|
||||
/** 依赖类型 → 边颜色 */
|
||||
function depTypeColor(depType: string): string {
|
||||
switch (depType) {
|
||||
@@ -378,4 +465,26 @@ onBeforeUnmount(() => {
|
||||
.dep-graph__modal-actions {
|
||||
display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 边删除按钮(HTML 遮罩层,绝对定位在 .dep-graph__canvas 上) */
|
||||
.dep-edge-delete-btn {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: var(--df-danger);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 200;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||
transition: background 0.15s;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.dep-edge-delete-btn:hover {
|
||||
background: #c24040;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user