Files
iboard/src/pages/task/BoardView.jsx
T
jif2.zhang 06e9a3e694 fix(任务): 编号改为 #序号展示、详情页完整编号可复制、修复详情改列报「看板列不存在」
- 列表/看板任务编号徽标改为 `#序号`(如 #18), 移除不再需要的 codePrefix 透传
- 详情页标题下新增完整任务编号胶囊 (项目编号-序号, 如 ADMIN-18), 点按复制:
  Clipboard API 优先, 非安全上下文/旧 WebView 回退 execCommand, 并提示已复制
- 修复详情页「看板列」切换报错: 0.2.1 本地库列 id 为 uuid 字符串,
  ActionSheet 的 key 恒为字符串, 旧代码 Number(key) 得到 NaN ->
  update 校验失败抛「看板列不存在」。改为字符串比对 (currentCol/勾选标记同步)
- 详情页保存后先就地替换该行再回源刷新
2026-10-27 10:06:12 +08:00

89 lines
3.4 KiB
React

import { useEffect, useRef, useState } from 'react'
import TaskCard from '../../components/task/TaskCard.jsx'
import useColumnDrag from '../../components/task/useColumnDrag.js'
// 列体 + 右缘滚动条: 滚动时出现细滚动条, 停止约 700ms 后淡出
// (移动端 WebView 原生滚动条是即隐式的, 看板列窄、需要明确的滚动指示)
function ScrollBody({ children }) {
const ref = useRef(null)
const fade = useRef(null)
const [bar, setBar] = useState(null) // { h, y } 滚动条几何
useEffect(() => () => clearTimeout(fade.current), [])
function onScroll() {
const el = ref.current
if (!el) return
const max = el.scrollHeight - el.clientHeight
if (max <= 2) { setBar(null); return }
const h = Math.max(28, Math.round(el.clientHeight * (el.clientHeight / el.scrollHeight)))
const y = Math.round((el.scrollTop / max) * (el.clientHeight - h))
setBar({ h, y })
clearTimeout(fade.current)
fade.current = setTimeout(() => setBar(null), 700)
}
return (
<div className="bcol-main">
<div className="bcol-body" ref={ref} onScroll={onScroll}>{children}</div>
<div className={'bcol-bar' + (bar ? ' on' : '')} style={bar ? { top: bar.y, height: bar.h } : undefined} />
</div>
)
}
// 看板视图 (T-BD-01, T-UI-07)
// 拖动 = 长按 400ms 激活, 卡片原位抬起, 指针划过列高亮, 松手落列 (重点设计 §7.1)
// 横向滚列; 列内独立纵向滚动, 列头固定, 滚动时右缘显示滚动条
// 不做"拖到卡片上转子任务" —— 统一走详情页「转为子任务」
export default function BoardView({
tasks, columns, onMoveColumn, onOpen, onToggleSub, onToggle, onMore, onQuickAdd,
}) {
const drag = useColumnDrag({ onDrop: onMoveColumn })
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${drag.overCol === col.id ? ' dragover' : ''}`}
key={col.id}
data-column-id={col.id}
>
<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>
<ScrollBody>
{list.map((t) => (
<TaskCard
key={t.id}
task={t}
dragging={drag.dragTask?.id === t.id}
bindDrag={drag.bind}
onClick={() => { if (!drag.dragTask && !drag.justEnded()) onOpen?.(t) }}
onToggleSub={onToggleSub}
onToggle={onToggle}
onMore={onMore}
/>
))}
{list.length === 0 && <div className="bcol-empty">暂无任务</div>}
<div className="badd" onClick={() => onQuickAdd?.(col.id)}>+ 添加任务</div>
</ScrollBody>
</div>
)
})}
</div>
)
}