- 列表/看板任务编号徽标改为 `#序号`(如 #18), 移除不再需要的 codePrefix 透传 - 详情页标题下新增完整任务编号胶囊 (项目编号-序号, 如 ADMIN-18), 点按复制: Clipboard API 优先, 非安全上下文/旧 WebView 回退 execCommand, 并提示已复制 - 修复详情页「看板列」切换报错: 0.2.1 本地库列 id 为 uuid 字符串, ActionSheet 的 key 恒为字符串, 旧代码 Number(key) 得到 NaN -> update 校验失败抛「看板列不存在」。改为字符串比对 (currentCol/勾选标记同步) - 详情页保存后先就地替换该行再回源刷新
85 lines
3.0 KiB
React
85 lines
3.0 KiB
React
import StatusDot from './StatusDot.jsx'
|
|
import Avatar from './Avatar.jsx'
|
|
import { kindColor } from '../../pages/task/kind.js'
|
|
import { taskShortId } from '../../pages/task/date.js'
|
|
|
|
// 看板卡片 (T-UI-02): 轻量化
|
|
// - 标题 + 状态点 (T-ST-09)
|
|
// - 负责人 12px/600 突出 (T-AS-01); 任务成员 10.5px 弱化置于下方 (T-AS-02)
|
|
// - 子任务内嵌简洁展示 + 可直接勾选 (T-ST-06/07)
|
|
const MAX_SUB = 3
|
|
|
|
export default function TaskCard({
|
|
task, bindDrag, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false,
|
|
}) {
|
|
const children = task.children || []
|
|
const shown = children.slice(0, MAX_SUB)
|
|
const rest = children.length - shown.length
|
|
// 编号只显示 `#序号`; 完整编号 (项目编号-序号) 放详情页 (见 TaskDetail)
|
|
const shortId = taskShortId(task)
|
|
|
|
return (
|
|
<div
|
|
className={`bcard${dragging ? ' dragging' : ''}${highlight ? ' hl' : ''}`}
|
|
data-task-id={task.id}
|
|
onClick={onClick}
|
|
{...(bindDrag ? bindDrag(task) : {})}
|
|
>
|
|
{/* 左侧竖排: 状态点 + ⋯ */}
|
|
<div className="bcard-left">
|
|
<span
|
|
className="bstat"
|
|
onClick={(e) => { e.stopPropagation(); onToggle?.(task) }}
|
|
onPointerDown={(e) => e.stopPropagation()}
|
|
title="点按切换完成"
|
|
>
|
|
<StatusDot status={task.status} />
|
|
</span>
|
|
{onMore && (
|
|
<span
|
|
className="bmore-btn"
|
|
onClick={(e) => { e.stopPropagation(); onMore(task) }}
|
|
onPointerDown={(e) => e.stopPropagation()}
|
|
>⋯</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="bcard-body">
|
|
<div className="brow1">
|
|
<span className="btitle">{task.title}</span>
|
|
</div>
|
|
|
|
<div className="bmeta">
|
|
{shortId && <span className="num-tag">{shortId}</span>}
|
|
{task.markDate && (
|
|
<span className="date-tag">{task.markDate.slice(5, 10).replace('-', '/')}</span>
|
|
)}
|
|
{task.kind && (
|
|
<span className="kind-tag" style={{ background: kindColor(task.kind) }}>{task.kind}</span>
|
|
)}
|
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, marginLeft: 'auto', minWidth: 0, justifyContent: 'flex-end' }}>
|
|
<Avatar name={task.assignee?.name} size={18} />
|
|
{task.members?.length > 0 && (
|
|
<span className="bmembers">+ {task.members.map((m) => m.name).join('、')}</span>
|
|
)}
|
|
</span>
|
|
</div>
|
|
|
|
{shown.map((c) => (
|
|
<div
|
|
key={c.id}
|
|
className={`bsub${c.status === 'done' ? ' done' : ''}`}
|
|
onClick={(e) => { e.stopPropagation(); onToggleSub?.(c) }}
|
|
onPointerDown={(e) => e.stopPropagation()}
|
|
>
|
|
<span className="sbox" />
|
|
<span className="stitle">{c.title}</span>
|
|
<StatusDot status={c.status} />
|
|
</div>
|
|
))}
|
|
{rest > 0 && <div className="bmore">+{rest} 个子任务</div>}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|