diff --git a/frontend/app/features/schedules/ArtifactList.tsx b/frontend/app/features/schedules/ArtifactList.tsx index e931b64..0399e57 100644 --- a/frontend/app/features/schedules/ArtifactList.tsx +++ b/frontend/app/features/schedules/ArtifactList.tsx @@ -1,8 +1,13 @@ +import { memo } from "react"; + import { type ScheduleArtifact } from "../../services/api"; import Icon from "../../components/common/Icon"; import { shortHash } from "./utils"; -export function ArtifactList({ +// 包裹 memo 让父级 SchedulePage 在画布交互(setSelectedNodeId / +// setSelectedEdgeId / linkSourceId 等)重渲染时,artifact 卡片不会跟着 +// 重建/重绑拖拽事件 —— 减少拖动过程中的事件重绑与列表抖动。 +function ArtifactListImpl({ artifacts, keyword, onDrop, @@ -77,3 +82,5 @@ export function ArtifactList({ ); } + +export const ArtifactList = memo(ArtifactListImpl); diff --git a/frontend/app/features/schedules/SchedulePage.tsx b/frontend/app/features/schedules/SchedulePage.tsx index 84b5a02..c30403d 100644 --- a/frontend/app/features/schedules/SchedulePage.tsx +++ b/frontend/app/features/schedules/SchedulePage.tsx @@ -3,6 +3,7 @@ import { type FormEvent, memo, type MouseEvent as ReactMouseEvent, + useCallback, useEffect, useMemo, useRef, @@ -307,6 +308,35 @@ export default function SchedulePage({ : artifacts; }, [artifacts, artifactKeyword]); + // 稳定传给 ArtifactList 的 3 个回调,让 memo 真正生效 —— 否则父级 + // (SchedulePage) 任何 store 字段变化都会让卡片列表重新绑定拖拽事件, + // 在拖动/快速 hover 时放大体感卡顿。 + const handleArtifactDrop = useCallback( + (artifact: ScheduleArtifact, x: number, y: number) => { + useSchedulesStore.getState().addArtifactAt(artifact, x, y); + }, + [], + ); + const handleArtifactDoubleClick = useCallback( + (artifact: ScheduleArtifact) => { + useSchedulesStore.getState().addArtifactAt( + artifact, + 90 + (schedule?.nodes.length ?? 0) * 245, + 130, + ); + }, + [schedule], + ); + const handleArtifactContextMenu = useCallback( + (event: ReactMouseEvent, artifact: ScheduleArtifact) => { + openScheduleContextMenu( + event as ReactMouseEvent, + { kind: "artifact", artifact }, + ); + }, + [], + ); + return (
@@ -364,9 +394,9 @@ export default function SchedulePage({ void useSchedulesStore.getState().addArtifactAt(artifact, x, y)} - onDoubleClick={(artifact) => void useSchedulesStore.getState().addArtifactAt(artifact, 90 + (schedule?.nodes.length ?? 0) * 245, 130)} - onContextMenu={(event, artifact) => openScheduleContextMenu(event as ReactMouseEvent, { kind: "artifact", artifact })} + onDrop={handleArtifactDrop} + onDoubleClick={handleArtifactDoubleClick} + onContextMenu={handleArtifactContextMenu} onKeywordChange={setArtifactKeyword} /> diff --git a/frontend/app/styles/schedule.css b/frontend/app/styles/schedule.css index 7bf2661..e610860 100644 --- a/frontend/app/styles/schedule.css +++ b/frontend/app/styles/schedule.css @@ -294,10 +294,10 @@ background: #fff; cursor: grab; user-select: none; - transition: - transform .15s ease, - box-shadow .15s ease, - border-color .15s ease; + /* hover 效果(border / box-shadow / transform)全部走合成层友好的属性, + 但禁止 transition:box-shadow 是 paint 操作,在 20+ 张卡片上快速 + 划过鼠标会触发频繁重绘,导致悬浮卡顿;拖动时也会让 drag image 变重、 + 视觉上"粘"在半动画上。直接瞬变,干净利落。 */ } .artifact-card:hover {