From aefeb20f3bbe6538c3d4cea1db692c200f530bd1 Mon Sep 17 00:00:00 2001 From: Aisland Date: Wed, 2 Sep 2026 00:58:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(console):=20=E5=AA=92=E4=BD=93=E5=BA=93?= =?UTF-8?q?=E6=8B=96=E6=8B=BD=E6=8E=92=E5=BA=8F=E5=8A=A0=20FLIP=20?= =?UTF-8?q?=E5=B9=B3=E6=BB=91=E5=8A=A8=E7=94=BB(160ms=20=E7=BC=93=E5=8A=A8?= =?UTF-8?q?)=E2=80=94=E2=80=94=E8=A1=8C=E6=BB=91=E5=8A=A8=E6=8D=A2?= =?UTF-8?q?=E4=BD=8D=E4=B8=8D=E5=86=8D=E8=B7=B3=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01_content/lib/client.js | 24 +++++++++++++++++++++++- 01_content/src/client/split.tsx | 22 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/01_content/lib/client.js b/01_content/lib/client.js index f44b46a..ac1cdc4 100644 --- a/01_content/lib/client.js +++ b/01_content/lib/client.js @@ -18086,7 +18086,22 @@ function ConsolePane() { if (next) void runUpdateCheck(false); }; const dragRowRef = (0, import_react.useRef)(null); + const dragFlipRef = (0, import_react.useRef)(null); const [dragRowId, setDragRowId] = (0, import_react.useState)(null); + (0, import_react.useLayoutEffect)(() => { + const first = dragFlipRef.current; + if (!first) return; + dragFlipRef.current = null; + document.querySelectorAll(".dsh-wt_photoRow").forEach((el) => { + const key = el.dataset.rid ?? ""; + const f = first.get(key); + if (!f) return; + const r = el.getBoundingClientRect(); + const dx = f.x - r.left; + const dy = f.y - r.top; + if (dx || dy) el.animate([{ transform: "translate(" + dx + "px," + dy + "px)" }, { transform: "none" }], { duration: 160, easing: "cubic-bezier(.22,.61,.36,1)" }); + }); + }, [photoList]); const onHandleDown = (id, e) => { if (e.button !== 0) return; e.preventDefault(); @@ -18096,6 +18111,12 @@ function ConsolePane() { const onMove = (ev) => { if (dragRowRef.current !== id) return; const rows = Array.from(document.querySelectorAll(".dsh-wt_photoRow")); + const rects = /* @__PURE__ */ new Map(); + rows.forEach((el) => { + const k = el.dataset.rid ?? ""; + const r = el.getBoundingClientRect(); + rects.set(k, { x: r.left, y: r.top }); + }); let target = 0; for (let i = 0; i < rows.length; i++) { const r = rows[i].getBoundingClientRect(); @@ -18107,6 +18128,7 @@ function ConsolePane() { const moved = next.splice(from, 1)[0]; const to = Math.max(0, Math.min(target > from ? target - 1 : target, next.length)); next.splice(to, 0, moved); + dragFlipRef.current = rects; setPhotoList(next); }; const onUp = () => { @@ -18516,7 +18538,7 @@ function ConsolePane() { /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-wt_switch" + (photoGrid ? " dsh-wt_switchOn" : ""), "aria-pressed": photoGrid, "aria-label": T("console.bgGridLabel"), onClick: togglePhotoGrid, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-wt_switchKnob" }) }) ] }) ] }), - photoList.length > 0 ? photoList.slice(0, 4).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-wt_dropRow dsh-wt_photoRow" + (photoId === p.id ? " dsh-wt_photoRowOn" : ""), onClick: () => selectPhoto(p), children: [ + photoList.length > 0 ? photoList.slice(0, 4).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-rid": p.id, className: "dsh-wt_dropRow dsh-wt_photoRow" + (photoId === p.id ? " dsh-wt_photoRowOn" : ""), onClick: () => selectPhoto(p), children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-wt_thumbWrap", children: [ p.kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { className: "dsh-wt_photoThumb", src: p.url, muted: true, playsInline: true, preload: "metadata" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { className: "dsh-wt_photoThumb", src: p.url, alt: "" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-wt_thumbBadge", "aria-hidden": true, children: p.kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 16 16", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.5 3.8l8 4.2-8 4.2z", fill: "currentColor" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "8", height: "8", viewBox: "0 0 16 16", children: [ diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index de733fa..f72321d 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -1574,7 +1574,23 @@ function ConsolePane() { } /** 抓手拖拽排序:按住抓手上下移动 → 行实时重排,松手持久化 */ const dragRowRef = useRef(null) + const dragFlipRef = useRef | null>(null) const [dragRowId, setDragRowId] = useState(null) + // FLIP:重排后旧位置 → 新位置的平滑滑动(160ms 缓动) + useLayoutEffect(() => { + const first = dragFlipRef.current + if (!first) return + dragFlipRef.current = null + document.querySelectorAll('.dsh-wt_photoRow').forEach((el) => { + const key = el.dataset.rid ?? '' + const f = first.get(key) + if (!f) return + const r = el.getBoundingClientRect() + const dx = f.x - r.left + const dy = f.y - r.top + if (dx || dy) el.animate([{ transform: 'translate(' + dx + 'px,' + dy + 'px)' }, { transform: 'none' }], { duration: 160, easing: 'cubic-bezier(.22,.61,.36,1)' }) + }) + }, [photoList]) const onHandleDown = (id: string, e: { button: number; preventDefault: () => void; stopPropagation: () => void }) => { if (e.button !== 0) return e.preventDefault() @@ -1584,6 +1600,9 @@ function ConsolePane() { const onMove = (ev: PointerEvent) => { if (dragRowRef.current !== id) return const rows = Array.from(document.querySelectorAll('.dsh-wt_photoRow')) + // 收集当前(重排前)位置用于 FLIP + const rects = new Map() + rows.forEach((el) => { const k = el.dataset.rid ?? ''; const r = el.getBoundingClientRect(); rects.set(k, { x: r.left, y: r.top }) }) let target = 0 for (let i = 0; i < rows.length; i++) { const r = rows[i].getBoundingClientRect() @@ -1595,6 +1614,7 @@ function ConsolePane() { const moved = next.splice(from, 1)[0] const to = Math.max(0, Math.min(target > from ? target - 1 : target, next.length)) next.splice(to, 0, moved) + dragFlipRef.current = rects setPhotoList(next) } const onUp = () => { @@ -1916,7 +1936,7 @@ function ConsolePane() { {photoList.length > 0 ? ( photoList.slice(0, 4).map((p) => ( -
selectPhoto(p)}> +
selectPhoto(p)}> {p.kind === 'video' ?