Docker Build and Push / build-image (push) Failing after 3m51s
需求 142 条 (T-*), 设计见 docs/V0.2/任务模块/ 数据层: - 新增 6 张表: Project / ProjectMember / BoardColumn / Task / TaskMember / TaskTag - 看板列是实体(可增删改排序), Task.columnId 外键 RESTRICT 防误删列丢任务 - Task.status(todo/doing/done) 与 columnId 双落库, 二者正交 领域层 server/task-domain/ (唯一口径, 前后端不重复实现): - resolveStatus: 列决定状态(只看目标列, 无拖出回退); 状态与列不匹配时按列序取第一个绑定该状态的列自动归位, 无匹配列则不移动; 子任务完全豁免 - retention: 逐列 6 档保留窗口(永不显示/当天/3天/本周/本月/永久显示), 以 doneAt 判定 - copyTask: 跨项目深拷贝, 人员按目标项目成员过滤并回报丢弃名单 接口: /api/projects, /api/columns, /api/tasks (均校验项目成员) 前端: - 底部 Tab 插入「任务」, 位于「打卡」之后; 默认首页仍为打卡 - 列表视图(子任务缩进+折叠) / 看板视图(长按拖拽换列、拖到卡片降为子任务) - 列设置(列管理+状态绑定+保留选项) / 任务详情(扩展信息卡片默认收起) / 成员管理 - 视图记忆与上次项目存设备端 已验证: 95 文件语法 0 错误、相对导入 0 缺失、领域逻辑用例全部通过
78 lines
2.9 KiB
React
78 lines
2.9 KiB
React
import { useState } from 'react'
|
|
import { Toast } from 'antd-mobile'
|
|
import TaskCard from '../../components/task/TaskCard.jsx'
|
|
import useLongPressDrag from '../../components/task/useLongPressDrag.js'
|
|
|
|
// 看板视图 (T-BD-01, T-UI-07, T-ST-06/12)
|
|
// 列横向滚动; 长按卡片拖拽换列 / 降为子任务
|
|
export default function BoardView({
|
|
tasks, columns, onMoveColumn, onMakeSubtask, onOpen, onToggleSub, onToggle, onQuickAdd,
|
|
}) {
|
|
const [overCol, setOverCol] = useState(null)
|
|
|
|
const drag = useLongPressDrag({
|
|
onDropColumn: (task, columnId) => { setOverCol(null); onMoveColumn?.(task, columnId) },
|
|
onDropTask: (task, parentId) => { setOverCol(null); onMakeSubtask?.(task, parentId) },
|
|
onReject: (msg) => { setOverCol(null); Toast.show({ content: msg }) },
|
|
})
|
|
|
|
const ordered = [...(columns || [])].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
|
|
return (
|
|
<>
|
|
<div className="board-wrap">
|
|
{ordered.map((col) => {
|
|
const list = (tasks || []).filter((t) => t.columnId === col.id)
|
|
return (
|
|
<div
|
|
className={`bcol${overCol === col.id ? ' dragover' : ''}`}
|
|
key={col.id}
|
|
data-column-id={col.id}
|
|
onDragEnter={() => setOverCol(col.id)}
|
|
onDragLeave={() => setOverCol((c) => (c === col.id ? null : c))}
|
|
>
|
|
<div className="bcol-head">
|
|
<span className="bcol-name">{col.name}</span>
|
|
<span className="bcol-cnt">{list.length}</span>
|
|
{col.boundStatus ? (
|
|
<span className="bcol-bind">
|
|
↔ {col.boundStatus === 'done' ? '完成' : col.boundStatus === 'doing' ? '执行中' : '未开始'}
|
|
</span>
|
|
) : (
|
|
<span className="bcol-bind off">未绑定</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="bcol-body">
|
|
{list.map((t) => (
|
|
<TaskCard
|
|
key={t.id}
|
|
task={t}
|
|
dragging={drag.dragging?.task?.id === t.id}
|
|
highlight={drag.dragging && drag.dragging.task.id !== t.id && overCol === col.id}
|
|
bindDrag={drag.bind}
|
|
onClick={() => { if (!drag.dragging) onOpen?.(t) }}
|
|
onToggleSub={onToggleSub}
|
|
onToggle={onToggle}
|
|
/>
|
|
))}
|
|
{list.length === 0 && <div className="bcol-empty">暂无任务</div>}
|
|
<div className="badd" onClick={() => onQuickAdd?.(col.id)}>+ 添加任务</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{drag.dragging && (
|
|
<div
|
|
className="drag-ghost"
|
|
style={{ left: drag.dragging.x, top: drag.dragging.y }}
|
|
>
|
|
{drag.dragging.task.title}
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|