perf(schedule): 减少重渲染与冗余动画
- useCanvasNodeDrag.moveDrag 改 ref 直改 DOM,松手才写 store,避免拖拽期间反复重渲染 SchedulePage - runs 轮询改 setTimeout 链式 + 移除 runs 依赖,避免每 1.5s 重建 interval - SchedulePage 拆 PositionDraftBadge / RunNowButton,删除冗余的 runs/runsLoading/positionDraftCount 订阅 - 拆 ScheduleEdge 组件 + React.memo + useMemo,切换 edge 选中时不再重算所有 edgePath - 删除 schedule.css 中引用未定义 keyframes 的 modal-in/spin 死动画 - 删除 .schedule-edge-line transition、.schedule-run-result-backdrop backdrop-filter;补 .artifact-card transition Refs: kaneo #19-#24
This commit is contained in:
@@ -9,13 +9,17 @@ type DragState = {
|
||||
startClientY: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
moved: boolean;
|
||||
nodeEl: HTMLDivElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* 画布节点拖拽编排。内部持有瞬时 dragRef,避免每次拖拽都触发 store 重渲染。
|
||||
* 拖拽过程中直接改 schedule.nodes 的 position;松手时把 rounded 后的位置写到 store 的
|
||||
* setPositionDraft 模块级 Map,刷新不丢。
|
||||
* 画布节点拖拽编排。内部持有瞬时 dragRef + 节点 DOM 引用,避免每次拖拽都触发 store
|
||||
* 重渲染:pointermove 期间直接改节点的 inline left/top(只动 DOM,不动 store);
|
||||
* 松手时把 rounded 后的位置一次性写入 store 的 setPositionDraft 模块级 Map(草稿
|
||||
* 持久化)+ setSchedule(让 React 知道最终位置),刷新不丢。
|
||||
*/
|
||||
export function useCanvasNodeDrag({
|
||||
busy,
|
||||
@@ -48,7 +52,10 @@ export function useCanvasNodeDrag({
|
||||
startClientY: event.clientY,
|
||||
originX: node.position_x,
|
||||
originY: node.position_y,
|
||||
currentX: node.position_x,
|
||||
currentY: node.position_y,
|
||||
moved: false,
|
||||
nodeEl: event.currentTarget,
|
||||
};
|
||||
setSelectedNodeId(node.node_id);
|
||||
setSelectedEdgeId(null);
|
||||
@@ -60,21 +67,13 @@ export function useCanvasNodeDrag({
|
||||
const deltaX = event.clientX - drag.startClientX;
|
||||
const deltaY = event.clientY - drag.startClientY;
|
||||
if (Math.abs(deltaX) + Math.abs(deltaY) > 3) drag.moved = true;
|
||||
setSchedule((current) => {
|
||||
if (!current) return current;
|
||||
return {
|
||||
...current,
|
||||
nodes: current.nodes.map((item) =>
|
||||
item.node_id === drag.nodeId
|
||||
? {
|
||||
...item,
|
||||
position_x: Math.max(10, drag.originX + deltaX),
|
||||
position_y: Math.max(10, drag.originY + deltaY),
|
||||
}
|
||||
: item,
|
||||
),
|
||||
};
|
||||
});
|
||||
const x = Math.max(10, drag.originX + deltaX);
|
||||
const y = Math.max(10, drag.originY + deltaY);
|
||||
drag.currentX = x;
|
||||
drag.currentY = y;
|
||||
// 只写 DOM,不动 store;拖拽期间 SchedulePage 不会因此重渲染。
|
||||
drag.nodeEl.style.left = `${x}px`;
|
||||
drag.nodeEl.style.top = `${y}px`;
|
||||
};
|
||||
|
||||
const finishDrag = (event: ReactPointerEvent<HTMLDivElement>): void => {
|
||||
@@ -82,20 +81,19 @@ export function useCanvasNodeDrag({
|
||||
if (!drag || drag.pointerId !== event.pointerId) return;
|
||||
dragRef.current = null;
|
||||
if (!drag.moved) return;
|
||||
const current = useSchedulesStore.getState().schedule;
|
||||
const node = current?.nodes.find((item) => item.node_id === drag.nodeId);
|
||||
if (!current || !node) return;
|
||||
const position = {
|
||||
position_x: Math.round(node.position_x),
|
||||
position_y: Math.round(node.position_y),
|
||||
position_x: Math.round(drag.currentX),
|
||||
position_y: Math.round(drag.currentY),
|
||||
};
|
||||
useSchedulesStore.getState().setPositionDraft(node.node_id, position);
|
||||
// 先记草稿(持久化),再把最终位置同步进 store 让节点 div 的 inline style 与
|
||||
// schedule.nodes 一致(下次重渲染/保存时不会出现位置回弹)。
|
||||
useSchedulesStore.getState().setPositionDraft(drag.nodeId, position);
|
||||
setSchedule((value) => {
|
||||
if (!value) return value;
|
||||
return {
|
||||
...value,
|
||||
nodes: value.nodes.map((item) =>
|
||||
item.node_id === node.node_id ? { ...item, ...position } : item,
|
||||
item.node_id === drag.nodeId ? { ...item, ...position } : item,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user