From 1673cbdb7dee7a0a96951ab913d1dff9e0d5925c Mon Sep 17 00:00:00 2001 From: "jif2.zhang" Date: Sun, 25 Oct 2026 02:19:03 +0800 Subject: [PATCH] =?UTF-8?q?refactor(V0.2):=20=E6=8B=96=E5=8A=A8=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E6=94=B6=E7=B4=A7=20=E2=80=94=E2=80=94=20=E4=BB=85?= =?UTF-8?q?=E6=8A=8A=E6=89=8B=E8=A7=A6=E5=8F=91=20/=20=E7=BC=A9=E5=B0=8F?= =?UTF-8?q?=E6=8A=8A=E6=89=8B=20/=20=E5=8E=BB=E5=A4=9A=E4=BD=99=E5=85=83?= =?UTF-8?q?=E7=B4=A0=20/=20=E6=89=93=E5=8D=A1=E8=B7=A8=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 打卡与列表: 去掉卡片整体长按拖动路径, 只有 ⠿ 把手可拖; 把手缩小 - 列表行去掉跟动的蓝色目标列徽标 (drag-pill), 占位/克隆浮层承担全部反馈 - 打卡支持跨组拖动: 落到其他周期组的目标卡 = 改周期 + 落位一次完成 (updateHabit period + reorder), 组内排序不变 - 看板: 去掉 ⠿ 把手, 恢复长按拖动 --- src/components/checkin/useHabitDrag.js | 8 ++++---- src/components/task/TaskCard.jsx | 5 +---- src/components/task/TaskRow.jsx | 9 ++------- src/pages/checkin/MergedView.jsx | 21 +++++++++++++++------ src/pages/task/BoardView.jsx | 1 - src/pages/task/ListView.jsx | 3 +-- src/styles/globals.css | 4 ++-- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/checkin/useHabitDrag.js b/src/components/checkin/useHabitDrag.js index a2f5c00..e21a1b2 100644 --- a/src/components/checkin/useHabitDrag.js +++ b/src/components/checkin/useHabitDrag.js @@ -62,12 +62,12 @@ export default function useHabitDrag({ onDrop }) { const el = document.elementFromPoint(x, y) const rowEl = el?.closest?.('[data-habit-row]') if (!rowEl) return null - // 只认同一周期组的落点 - if (rowEl.dataset.habitPeriod !== st.current.item?.period) return null const r = rowEl.getBoundingClientRect() const id = Number(rowEl.dataset.habitId) if (id === st.current.item.id) return null - return { id, after: y > r.top + r.height / 2 } + // 允许跨组: 落到其他周期组的目标卡上 = 同时改周期 + 落位 (由 onDrop 处理) + const period = rowEl.dataset.habitPeriod + return { id, period, after: y > r.top + r.height / 2 } } const onMove = (ev) => { @@ -89,7 +89,7 @@ export default function useHabitDrag({ onDrop }) { const s = st.current if (s.active && s.item) { lastEnd.current = Date.now() - if (s.over && s.over.id !== s.item.id) onDropRef.current?.(s.item, s.over.id, s.over.after) + if (s.over && s.over.id !== s.item.id) onDropRef.current?.(s.item, s.over.id, s.over.after, s.over.period) } cleanup() } diff --git a/src/components/task/TaskCard.jsx b/src/components/task/TaskCard.jsx index dec07c7..c7ed88a 100644 --- a/src/components/task/TaskCard.jsx +++ b/src/components/task/TaskCard.jsx @@ -7,7 +7,7 @@ import StatusDot from './StatusDot.jsx' const MAX_SUB = 3 export default function TaskCard({ - task, bindDrag, bindDragHandle, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false, + task, bindDrag, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false, }) { const children = task.children || [] const shown = children.slice(0, MAX_SUB) @@ -20,9 +20,6 @@ export default function TaskCard({ onClick={onClick} {...(bindDrag ? bindDrag(task) : {})} > - {bindDragHandle && ( - ⠿ - )}
{task.title} (s === 'done' ? '完成' : s === 'doing' ? '执行中 // 复选框 / ⋯ 是独立控件: pointerdown 不冒泡, 不发起拖动, // 避免长按行后抬手时合成的 click 落在控件上误触状态切换 export default function TaskRow({ - task, onToggle, onClick, onMore, bindDrag, bindDragHandle, dragTarget, dragOver, indent = false, children, + task, onToggle, onClick, onMore, bindDrag, bindDragHandle, dragging = false, dragOver, indent = false, children, }) { const done = task.status === 'done' return ( @@ -16,7 +16,7 @@ export default function TaskRow({
- {dragTarget && ( - - → {dragTarget.name} - - )} { e.stopPropagation(); onMore?.(task) }} diff --git a/src/pages/checkin/MergedView.jsx b/src/pages/checkin/MergedView.jsx index 9424394..60a09ee 100644 --- a/src/pages/checkin/MergedView.jsx +++ b/src/pages/checkin/MergedView.jsx @@ -100,18 +100,28 @@ export default function MergedView({ date, onDateChange }) { const drag = useHabitDrag({ onDrop: reorderHabit }) - // 组内拖动落位: 重排后按渲染分组顺序整体展平重写 sortOrder - async function reorderHabit(dragged, targetId, after) { + // 拖动落位: 组内排序; 落到其他周期组 = 同时改周期并落位。 + // 重排后按渲染分组顺序整体展平重写 sortOrder。 + async function reorderHabit(dragged, targetId, after, targetPeriod) { const snapshot = detail - const g = groups.find((x) => x.key === (GROUP_ORDER.includes(dragged.period) ? dragged.period : 'other')) + const g = groups.find((x) => x.items.some((i) => i.id === targetId)) if (!g) return + const target = g.items.find((i) => i.id === targetId) const rest = g.items.filter((i) => i.id !== dragged.id) const ti = rest.findIndex((i) => i.id === targetId) if (ti < 0) return - rest.splice(after ? ti + 1 : ti, 0, dragged) - const flat = groups.map((x) => (x.key === g.key ? rest : x.items)).flat() + const crossed = targetPeriod && targetPeriod !== dragged.period + const moved = crossed + ? { ...dragged, period: targetPeriod, periodLabel: GROUP_LABEL[targetPeriod] || target.periodLabel } + : dragged + rest.splice(after ? ti + 1 : ti, 0, moved) + // 展平时把拖动项从原组剔除 (跨组场景原组可能仍包含它) + const flat = groups + .map((x) => (x.key === g.key ? rest : x.items.filter((i) => i.id !== dragged.id))) + .flat() setDetail((d) => ({ ...d, items: flat.map((it, i) => ({ ...it, sortOrder: i })) })) try { + if (crossed) await checkinApi.updateHabit(dragged.id, { period: targetPeriod }) await checkinApi.reorderHabits(flat.map((i) => i.id)) } catch (e) { setDetail(snapshot) @@ -324,7 +334,6 @@ export default function MergedView({ date, onDateChange }) { + (drag.dragItem?.id === item.id ? ' dragging' : '') + (drag.over?.id === item.id ? ` dragover ${drag.over.after ? 'after' : 'before'}` : '') } - {...drag.bind(item)} > ⠿ { if (!drag.dragTask && !drag.justEnded()) onOpen?.(t) }} onToggleSub={onToggleSub} onToggle={onToggle} diff --git a/src/pages/task/ListView.jsx b/src/pages/task/ListView.jsx index 932516c..326abff 100644 --- a/src/pages/task/ListView.jsx +++ b/src/pages/task/ListView.jsx @@ -52,9 +52,8 @@ export default function ListView({ onToggle={onToggle} onClick={() => { if (!drag.justEnded()) onOpen(t) }} onMore={onMore} - bindDrag={drag.bind} bindDragHandle={drag.bindHandle} - dragTarget={dragging ? orderedCols[dragging.toIdx] : null} + dragging={!!dragging} dragOver={drag.over?.id === t.id ? drag.over : null} /> {kids.length > 0 && ( diff --git a/src/styles/globals.css b/src/styles/globals.css index 90ab25c..c512973 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -682,8 +682,8 @@ body.me-safe-top .app-safe-top { height: 0; } 指针流不会被滚动抢占取消, 是 Android WebView 上唯一可靠的拖动入口 */ .grip { touch-action: none; -webkit-user-select: none; user-select: none; - color: #c2c7cf; font-size: 14px; line-height: 1; - padding: 14px 6px; margin: -8px 0; min-width: 26px; min-height: 36px; + color: #c2c7cf; font-size: 12px; line-height: 1; + padding: 10px 3px; margin: -6px 0; min-width: 20px; min-height: 28px; display: inline-flex; align-items: center; justify-content: center; cursor: grab; flex-shrink: 0; align-self: center; }