diff --git a/static/app.js b/static/app.js index f485c5c..bcd2aa6 100644 --- a/static/app.js +++ b/static/app.js @@ -324,16 +324,25 @@ function dagSize() { } function dagFitView() { - // 画布适配视口:缩放并居中,让全部节点可见 + // 画布适配视口:基于节点实际包围盒缩放并居中,让全部节点可见 const s = dagState; const box = $('#dag-box'); - const {width, height} = dagSize(); if (!box || !s) return; + const poss = Object.values(s.pos || {}); + if (!poss.length) return; + const minX = Math.min(...poss.map(p => p.x)) - 120; + const maxX = Math.max(...poss.map(p => p.x)) + 120; + const minY = Math.min(...poss.map(p => p.y)) - 120; + const maxY = Math.max(...poss.map(p => p.y)) + 120; + const cw = Math.max(200, maxX - minX), ch = Math.max(200, maxY - minY); const bw = box.clientWidth, bh = box.clientHeight; - const scale = Math.max(0.25, Math.min(1, (bw - 60) / width, (bh - 60) / height)); + const scale = Math.max(0.25, Math.min(1, (bw - 60) / cw, (bh - 60) / ch)); s.scale = scale; - s.tx = (bw - width * scale) / 2; - s.ty = (bh - height * scale) / 2; + // 节点包围盒中心(viewBox 坐标)对准视口中心 + const cx = DAG_M + (minX + maxX) / 2; + const cy = DAG_M + (minY + maxY) / 2; + s.tx = bw / 2 - cx * scale; + s.ty = bh / 2 - cy * scale; dagApplyView(); saveDagState(); } @@ -382,6 +391,7 @@ function drawDag() { const body = $('#tab-body'); if (!body) return; const NW = 140, NHW = 70; // 节点宽 / 半宽 + const M = DAG_M; // svg 盒边距:节点平移/缩放后仍在盒内可命中 const dagText = t => { const raw = `#${t.id} ${t.title}`; return raw.length > 13 ? raw.slice(0, 13) + '…' : raw; @@ -397,9 +407,9 @@ function drawDag() {
🗑️ 删除区0
- + - + ${edges.map(e => { const a = dagState.pos[e.from], b = dagState.pos[e.to]; if (!a || !b) return ''; @@ -426,6 +436,7 @@ function drawDag() { let dagBound = false; // window 级监听只绑定一次 let dagDrag = null; // 模块级拖拽状态(box 重绑后 window handler 仍可访问) +const DAG_M = 3000; // svg 盒边距:节点平移/缩放后仍在盒内可命中 function bindDagEvents() { const box = $('#dag-box'); if (!box) return; @@ -509,7 +520,7 @@ function bindDagEvents() { if (!svgEl2) return; svgEl2.querySelectorAll('.dag-node').forEach(g => { const nid = Number(g.dataset.id); - const pp = s.pos[nid]; + const pp = st.pos[nid]; if (!pp) return; g.querySelector('rect').setAttribute('x', pp.x - 70); g.querySelector('rect').setAttribute('y', pp.y - 24); @@ -527,7 +538,7 @@ function bindDagEvents() { projCtx.tasks.forEach(t => { if (t.id !== tid) return; (t.depends_on || []).forEach(d => { - const b = s.pos[d]; + const b = st.pos[d]; if (!b) return; const path = svgEl2.querySelector(`path[data-e="${d}-${tid}"]`); if (!path) return; @@ -662,20 +673,20 @@ function renderDag() { const body2 = $('#tab-body'); body2.innerHTML = ''; drawDag(); - // 节点是否在视口内?不在则自动适配(防止节点被平移到视口外无法操作) + // 节点是否全部在视口内?任一越界则自动适配(防止节点被平移到视口外无法操作) const box = $('#dag-box'); const bb = box ? box.getBoundingClientRect() : null; - let anyVisible = false; + let allVisible = !bb; if (bb) { - tasks.forEach(t => { + allVisible = tasks.every(t => { const p = dagState.pos[t.id]; - if (!p) return; - const vx = bb.left + dagState.tx + p.x * dagState.scale; - const vy = bb.top + dagState.ty + p.y * dagState.scale; - if (vx < bb.right && vx > bb.left && vy < bb.bottom && vy > bb.top) anyVisible = true; + if (!p) return true; + const vx = bb.left + dagState.tx + (DAG_M + p.x) * dagState.scale; + const vy = bb.top + dagState.ty + (DAG_M + p.y) * dagState.scale; + return vx < bb.right && vx > bb.left && vy < bb.bottom && vy > bb.top; }); } - if (!anyVisible || !savedView) dagFitView(); + if (!allVisible || !savedView) dagFitView(); } async function runWorkflow() {