Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cbe3a5996 |
+28
-17
@@ -324,16 +324,25 @@ function dagSize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dagFitView() {
|
function dagFitView() {
|
||||||
// 画布适配视口:缩放并居中,让全部节点可见
|
// 画布适配视口:基于节点实际包围盒缩放并居中,让全部节点可见
|
||||||
const s = dagState;
|
const s = dagState;
|
||||||
const box = $('#dag-box');
|
const box = $('#dag-box');
|
||||||
const {width, height} = dagSize();
|
|
||||||
if (!box || !s) return;
|
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 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.scale = scale;
|
||||||
s.tx = (bw - width * scale) / 2;
|
// 节点包围盒中心(viewBox 坐标)对准视口中心
|
||||||
s.ty = (bh - height * scale) / 2;
|
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();
|
dagApplyView();
|
||||||
saveDagState();
|
saveDagState();
|
||||||
}
|
}
|
||||||
@@ -382,6 +391,7 @@ function drawDag() {
|
|||||||
const body = $('#tab-body');
|
const body = $('#tab-body');
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
const NW = 140, NHW = 70; // 节点宽 / 半宽
|
const NW = 140, NHW = 70; // 节点宽 / 半宽
|
||||||
|
const M = DAG_M; // svg 盒边距:节点平移/缩放后仍在盒内可命中
|
||||||
const dagText = t => {
|
const dagText = t => {
|
||||||
const raw = `#${t.id} ${t.title}`;
|
const raw = `#${t.id} ${t.title}`;
|
||||||
return raw.length > 13 ? raw.slice(0, 13) + '…' : raw;
|
return raw.length > 13 ? raw.slice(0, 13) + '…' : raw;
|
||||||
@@ -397,9 +407,9 @@ function drawDag() {
|
|||||||
<button onclick="dagZoomTo(1)" title="一键回到 100% 原始视图">100%</button>
|
<button onclick="dagZoomTo(1)" title="一键回到 100% 原始视图">100%</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="dag-trash" id="dag-trash" onclick="openDagTrash()" title="点击查看回收站">🗑️ 删除区<span class="cnt" id="dag-trash-cnt">0</span></div>
|
<div class="dag-trash" id="dag-trash" onclick="openDagTrash()" title="点击查看回收站">🗑️ 删除区<span class="cnt" id="dag-trash-cnt">0</span></div>
|
||||||
<svg id="dag-svg" width="${width}" height="${Math.max(480, height)}" viewBox="0 0 ${width} ${height}" style="transform-origin:0 0">
|
<svg id="dag-svg" width="${width + M * 2}" height="${Math.max(480, height) + M * 2}" viewBox="${-M} ${-M} ${width + M * 2} ${height + M * 2}" style="transform-origin:0 0">
|
||||||
<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#4a5878"/></marker></defs>
|
<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#4a5878"/></marker></defs>
|
||||||
<rect id="dag-bg" x="-4000" y="-4000" width="${width + 8000}" height="${height + 8000}" fill="var(--panel)"/>
|
<rect id="dag-bg" x="${-M - 1000}" y="${-M - 1000}" width="${width + 2 * M + 2000}" height="${height + 2 * M + 2000}" fill="var(--panel)"/>
|
||||||
${edges.map(e => {
|
${edges.map(e => {
|
||||||
const a = dagState.pos[e.from], b = dagState.pos[e.to];
|
const a = dagState.pos[e.from], b = dagState.pos[e.to];
|
||||||
if (!a || !b) return '';
|
if (!a || !b) return '';
|
||||||
@@ -426,6 +436,7 @@ function drawDag() {
|
|||||||
|
|
||||||
let dagBound = false; // window 级监听只绑定一次
|
let dagBound = false; // window 级监听只绑定一次
|
||||||
let dagDrag = null; // 模块级拖拽状态(box 重绑后 window handler 仍可访问)
|
let dagDrag = null; // 模块级拖拽状态(box 重绑后 window handler 仍可访问)
|
||||||
|
const DAG_M = 3000; // svg 盒边距:节点平移/缩放后仍在盒内可命中
|
||||||
function bindDagEvents() {
|
function bindDagEvents() {
|
||||||
const box = $('#dag-box');
|
const box = $('#dag-box');
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
@@ -509,7 +520,7 @@ function bindDagEvents() {
|
|||||||
if (!svgEl2) return;
|
if (!svgEl2) return;
|
||||||
svgEl2.querySelectorAll('.dag-node').forEach(g => {
|
svgEl2.querySelectorAll('.dag-node').forEach(g => {
|
||||||
const nid = Number(g.dataset.id);
|
const nid = Number(g.dataset.id);
|
||||||
const pp = s.pos[nid];
|
const pp = st.pos[nid];
|
||||||
if (!pp) return;
|
if (!pp) return;
|
||||||
g.querySelector('rect').setAttribute('x', pp.x - 70);
|
g.querySelector('rect').setAttribute('x', pp.x - 70);
|
||||||
g.querySelector('rect').setAttribute('y', pp.y - 24);
|
g.querySelector('rect').setAttribute('y', pp.y - 24);
|
||||||
@@ -527,7 +538,7 @@ function bindDagEvents() {
|
|||||||
projCtx.tasks.forEach(t => {
|
projCtx.tasks.forEach(t => {
|
||||||
if (t.id !== tid) return;
|
if (t.id !== tid) return;
|
||||||
(t.depends_on || []).forEach(d => {
|
(t.depends_on || []).forEach(d => {
|
||||||
const b = s.pos[d];
|
const b = st.pos[d];
|
||||||
if (!b) return;
|
if (!b) return;
|
||||||
const path = svgEl2.querySelector(`path[data-e="${d}-${tid}"]`);
|
const path = svgEl2.querySelector(`path[data-e="${d}-${tid}"]`);
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
@@ -662,20 +673,20 @@ function renderDag() {
|
|||||||
const body2 = $('#tab-body');
|
const body2 = $('#tab-body');
|
||||||
body2.innerHTML = '';
|
body2.innerHTML = '';
|
||||||
drawDag();
|
drawDag();
|
||||||
// 节点是否在视口内?不在则自动适配(防止节点被平移到视口外无法操作)
|
// 节点是否全部在视口内?任一越界则自动适配(防止节点被平移到视口外无法操作)
|
||||||
const box = $('#dag-box');
|
const box = $('#dag-box');
|
||||||
const bb = box ? box.getBoundingClientRect() : null;
|
const bb = box ? box.getBoundingClientRect() : null;
|
||||||
let anyVisible = false;
|
let allVisible = !bb;
|
||||||
if (bb) {
|
if (bb) {
|
||||||
tasks.forEach(t => {
|
allVisible = tasks.every(t => {
|
||||||
const p = dagState.pos[t.id];
|
const p = dagState.pos[t.id];
|
||||||
if (!p) return;
|
if (!p) return true;
|
||||||
const vx = bb.left + dagState.tx + p.x * dagState.scale;
|
const vx = bb.left + dagState.tx + (DAG_M + p.x) * dagState.scale;
|
||||||
const vy = bb.top + dagState.ty + p.y * dagState.scale;
|
const vy = bb.top + dagState.ty + (DAG_M + p.y) * dagState.scale;
|
||||||
if (vx < bb.right && vx > bb.left && vy < bb.bottom && vy > bb.top) anyVisible = true;
|
return vx < bb.right && vx > bb.left && vy < bb.bottom && vy > bb.top;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!anyVisible || !savedView) dagFitView();
|
if (!allVisible || !savedView) dagFitView();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runWorkflow() {
|
async function runWorkflow() {
|
||||||
|
|||||||
Reference in New Issue
Block a user