fix(V0.2): 三轮反馈 5 项 —— 回滚键盘方案 / 列表卡片拖动 / 看板防误触 / 横滑整屏 / 详情页恢复
Docker Build and Push / build-image (push) Successful in 4m9s
Docker Build and Push / build-image (push) Successful in 4m9s
1. 回滚键盘遮挡统一方案 (真机测试不通过, 需求搁置): 恢复 TaskDetail/ UserPicker/CopyTaskPopup/ProjectManage 与 useKeyboardInset 到 9fea1a5^, 删除 useKeyboardInsetWatcher / .kb-safe / interactive-widget=resizes-content 2. 任务详情页样式随 1 一并恢复提交前状态 (含底部拷贝/删除按钮) 3. 列表视图换列改为卡片拖动: 新 useRowDrag (长按 400ms 行抬起, 左右 80px/列步进, 行尾徽标显示目标列, 松手落列), 移除长按 ActionSheet —— 旧长按与复选框/行点击互相误触, 抬手合成 click 落在控件上触发状态切换 4. 看板拖动防误触: 任务区禁长按文字选择与系统菜单 (输入框除外); pointercancel 只放弃不提交; 拖动结束 350ms 内抑制 click 误开详情; 状态点/⋯/子任务行 pointerdown 不冒泡, 不发起拖动 5. 看板横滑扩展到整屏: .page-transition 补 height:100% 打通 app-main → page-with-navbar 百分比链, board-wrap 撑满剩余高度 已验证: kb-safe/Watcher/onLongPress 残留引用清零, 逐文件 diff 审阅通过; 本机无 Node 运行时, 未跑 vite build
This commit is contained in:
@@ -1,49 +1,25 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import StatusDot from './StatusDot.jsx'
|
||||
|
||||
// 列表视图的一行 (Todoist 风格, T-UI-01)
|
||||
// 长按 500ms → onLongPress (移动到列菜单); 点击其他区域 → onClick (编辑)
|
||||
const LONG_PRESS = 500
|
||||
|
||||
// 点击行体 → 打开详情; 长按 400ms 后左右拖动换列 (bindDrag 来自 useRowDrag)
|
||||
// 复选框 / ⋯ 是独立控件: pointerdown 不冒泡, 不发起拖动,
|
||||
// 避免长按行后抬手时合成的 click 落在控件上误触状态切换
|
||||
export default function TaskRow({
|
||||
task, onToggle, onClick, onMore, onLongPress, indent = false, children,
|
||||
task, onToggle, onClick, onMore, bindDrag, dragTarget, indent = false, children,
|
||||
}) {
|
||||
const timer = useRef(null)
|
||||
const moved = useRef(false)
|
||||
|
||||
const clear = () => { if (timer.current) { clearTimeout(timer.current); timer.current = null } }
|
||||
useEffect(() => () => clear(), [])
|
||||
|
||||
const startPress = () => {
|
||||
moved.current = false
|
||||
clear()
|
||||
if (!onLongPress) return
|
||||
timer.current = setTimeout(() => {
|
||||
if (!moved.current) {
|
||||
if (navigator.vibrate) navigator.vibrate(15)
|
||||
onLongPress(task)
|
||||
}
|
||||
}, LONG_PRESS)
|
||||
}
|
||||
const cancelPress = () => clear()
|
||||
const markMoved = () => { moved.current = true; clear() }
|
||||
|
||||
const done = task.status === 'done'
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${indent ? 'sub-row' : 'task-row'}${done ? ' done' : ''}`}
|
||||
className={`${indent ? 'sub-row' : 'task-row'}${done ? ' done' : ''}${dragTarget ? ' dragging' : ''}`}
|
||||
data-task-id={indent ? undefined : task.id}
|
||||
onClick={() => { if (!moved.current) onClick?.(task) }}
|
||||
onTouchStart={startPress}
|
||||
onTouchMove={markMoved}
|
||||
onTouchEnd={cancelPress}
|
||||
onMouseDown={startPress}
|
||||
onMouseUp={cancelPress}
|
||||
onClick={onClick}
|
||||
{...(!indent && bindDrag ? bindDrag(task) : {})}
|
||||
>
|
||||
<div
|
||||
className="box"
|
||||
onClick={(e) => { e.stopPropagation(); onToggle?.(task) }}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<svg viewBox="0 0 14 14" width={indent ? 10 : 11} height={indent ? 10 : 11}>
|
||||
<path d="M2.5 7.2l3 3 6-6.4" stroke="#fff" strokeWidth="2" fill="none" strokeLinecap="round" />
|
||||
@@ -72,7 +48,16 @@ export default function TaskRow({
|
||||
</div>
|
||||
</div>
|
||||
<div className="task-right">
|
||||
<span className="more" onClick={(e) => { e.stopPropagation(); onMore?.(task) }}>⋯</span>
|
||||
{dragTarget && (
|
||||
<span className={`drag-pill${dragTarget.id === task.columnId ? ' cur' : ''}`}>
|
||||
→ {dragTarget.name}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="more"
|
||||
onClick={(e) => { e.stopPropagation(); onMore?.(task) }}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
>⋯</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user