- 列表/看板任务编号徽标改为 `#序号`(如 #18), 移除不再需要的 codePrefix 透传 - 详情页标题下新增完整任务编号胶囊 (项目编号-序号, 如 ADMIN-18), 点按复制: Clipboard API 优先, 非安全上下文/旧 WebView 回退 execCommand, 并提示已复制 - 修复详情页「看板列」切换报错: 0.2.1 本地库列 id 为 uuid 字符串, ActionSheet 的 key 恒为字符串, 旧代码 Number(key) 得到 NaN -> update 校验失败抛「看板列不存在」。改为字符串比对 (currentCol/勾选标记同步) - 详情页保存后先就地替换该行再回源刷新
80 lines
3.0 KiB
React
80 lines
3.0 KiB
React
import { useState } from 'react'
|
|
import TaskRow from '../../components/task/TaskRow.jsx'
|
|
import useRowDrag from '../../components/task/useRowDrag.js'
|
|
|
|
// 列表视图 (T-VW-01, T-ST-04/05)
|
|
// 按看板列分组展示; 子任务缩进 + 折叠展开
|
|
// 换列: 行/把手拖动, 跟手浮层, 手指划到哪个分组松手就落到哪 (与看板一致)
|
|
// (列表只展示有任务的列, 空列需到看板视图拖入)
|
|
export default function ListView({
|
|
tasks, columns, onToggle, onOpen, onMore, onMoveColumn, onReorder,
|
|
}) {
|
|
const [collapsed, setCollapsed] = useState({})
|
|
const drag = useRowDrag({ columns, onDrop: onMoveColumn, onReorder })
|
|
|
|
if (!tasks || tasks.length === 0) {
|
|
return (
|
|
<div className="empty" style={{ paddingTop: 90 }}>
|
|
<div className="ico">✓</div>
|
|
<p>这个项目还没有任务</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 分组: 按列顺序, 未匹配列的任务归到一起
|
|
const groups = []
|
|
const orderedCols = [...(columns || [])].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
for (const c of orderedCols) {
|
|
const list = tasks.filter((t) => t.columnId === c.id)
|
|
if (list.length) groups.push({ key: c.id, name: c.name, list })
|
|
}
|
|
const known = new Set(orderedCols.map((c) => c.id))
|
|
const orphans = tasks.filter((t) => !known.has(t.columnId))
|
|
if (orphans.length) groups.push({ key: 'other', name: '其他', list: orphans })
|
|
|
|
return (
|
|
<div className="list-body">
|
|
{groups.map((g) => (
|
|
<div key={g.key} data-column-id={g.key === 'other' ? undefined : g.key}>
|
|
<div className="group-title">
|
|
{g.name}
|
|
<span className="cnt">{g.list.length}</span>
|
|
</div>
|
|
|
|
{g.list.map((t) => {
|
|
const kids = t.children || []
|
|
const isCollapsed = !!collapsed[t.id]
|
|
const dragging = drag.drag?.task?.id === t.id ? drag.drag : null
|
|
return (
|
|
<div key={t.id}>
|
|
<TaskRow
|
|
task={t}
|
|
onToggle={onToggle}
|
|
onClick={() => { if (!drag.justEnded()) onOpen(t) }}
|
|
onMore={onMore}
|
|
bindDragHandle={drag.bindHandle}
|
|
dragging={!!dragging}
|
|
dragOver={drag.over?.id === t.id ? drag.over : null}
|
|
/>
|
|
{kids.length > 0 && (
|
|
<>
|
|
{!isCollapsed && kids.map((c) => (
|
|
<TaskRow key={c.id} task={c} indent onToggle={onToggle} onClick={() => onOpen(c)} onMore={onMore} />
|
|
))}
|
|
<div
|
|
className="collapse"
|
|
onClick={() => setCollapsed((p) => ({ ...p, [t.id]: !p[t.id] }))}
|
|
>
|
|
{isCollapsed ? `展开 ${kids.length} 个子任务 ⌄` : `收起 ${kids.length} 个子任务 ⌃`}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|