feat(V0.2): 任务操作菜单 / 状态指示 / 移动与转子任务 / 移除扩展信息
Docker Build and Push / build-image (push) Successful in 4m13s
Docker Build and Push / build-image (push) Successful in 4m13s
1. 任务 ⋯ 菜单 (列表行与看板卡片): 编辑 / 拷贝到其他项目 / 删除 (antd-mobile ActionSheet, 带任务名标题; 拷贝抽出为 CopyTaskPopup 复用) 2. 点击任务非按钮区域 = 打开编辑页 (原本即如此, ⋯ 不再误触为编辑) 3. 修复状态选择器堆成三行: 此前 .seg 只有 .pv-head 作用域样式, 详情弹层裸用导致纵向堆叠; 新增通用 .seg 分段样式, 当前项高亮点按即切 4. 移除任务的扩展信息 UI (T-TK-04~07 标注已取消, 决策 35): ExtrasCard 组件删除, DB extras 字段保留不再使用; 项目相关长文本由项目信息块 (T-PJ-13) 承载 5. 任务移动入口: 详情页新增「看板列」ActionSheet 换列(联动裁决不变) 与「层级」区(转为子任务=选父任务 / 转为顶层), 覆盖无法拖拽的场景 6. 列表页移除「+ 子任务」入口: 添加子任务只在详情页, 或经转子任务 已验证: 99 文件语法 0 错误、导入 0 缺失、文档 149 条/决策 35 连续
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { DotLoading, Popup, Toast } from 'antd-mobile'
|
||||
import { projectApi, taskApi } from '../../api/task.js'
|
||||
|
||||
// 跨项目拷贝弹层 (T-CP-*): 选择目标项目 → 深拷贝; 人员不属于目标项目时丢弃并提示
|
||||
export default function CopyTaskPopup({ task, visible, onClose, onDone }) {
|
||||
const [projects, setProjects] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return
|
||||
setProjects(null)
|
||||
projectApi.list()
|
||||
.then((d) => setProjects((d.items || []).filter((p) => p.id !== task.projectId && !p.archived)))
|
||||
.catch((e) => {
|
||||
Toast.show({ content: e.message })
|
||||
setProjects([])
|
||||
})
|
||||
}, [visible, task.projectId])
|
||||
|
||||
async function doCopy(projectId, name) {
|
||||
try {
|
||||
const r = await taskApi.copy(task.id, projectId)
|
||||
const dropped = r.dropped || {}
|
||||
const lost = [dropped.assignee?.name, ...(dropped.members || []).map((m) => m.name)].filter(Boolean)
|
||||
Toast.show({
|
||||
content: lost.length
|
||||
? `已拷贝到「${name}」;${lost.join('、')} 不是该项目成员,已不保留`
|
||||
: `已拷贝到「${name}」`,
|
||||
})
|
||||
onClose?.()
|
||||
onDone?.()
|
||||
} catch (e) {
|
||||
Toast.show({ content: e.message })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup
|
||||
visible={visible}
|
||||
onMaskClick={onClose}
|
||||
bodyStyle={{
|
||||
borderTopLeftRadius: 12, borderTopRightRadius: 12,
|
||||
padding: '16px 16px calc(env(safe-area-inset-bottom, 0px) + 20px)',
|
||||
}}
|
||||
>
|
||||
<div className="sheet-title">跨项目拷贝</div>
|
||||
<div className="sheet-sub">「{task.title}」→ 选择目标项目;源任务保持不变</div>
|
||||
|
||||
{!projects && <div className="empty" style={{ padding: '20px 0' }}><DotLoading color="#1677ff" /> 加载项目…</div>}
|
||||
{projects && projects.length === 0 && <div className="extras-empty">没有其他可拷贝的项目</div>}
|
||||
{(projects || []).map((p) => (
|
||||
<div className="pick-row" key={p.id} onClick={() => doCopy(p.id, p.name)}>
|
||||
<div className="avatar">{(p.name || '?').slice(0, 1)}</div>
|
||||
<span className="pn">{p.name}</span>
|
||||
<span className="link">拷贝</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="btn-ghost" onClick={onClose}>取消</div>
|
||||
</Popup>
|
||||
)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
// 扩展信息卡片 (T-TK-04~07): 任意条目, 默认收起, 全部非必填
|
||||
// 条目结构 { label, value, url? }
|
||||
export default function ExtrasCard({ extras = [], onChange, readOnly = false }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const list = Array.isArray(extras) ? extras : []
|
||||
|
||||
const update = (idx, key, val) => {
|
||||
const next = list.map((e, i) => (i === idx ? { ...e, [key]: val } : e))
|
||||
onChange?.(next)
|
||||
}
|
||||
|
||||
const add = () => onChange?.([...list, { label: '', value: '', url: '' }])
|
||||
const remove = (idx) => onChange?.(list.filter((_, i) => i !== idx))
|
||||
|
||||
return (
|
||||
<div className="extras">
|
||||
<div className="extras-head" onClick={() => setOpen((v) => !v)}>
|
||||
<span className="caret">{open ? '▾' : '▸'}</span>
|
||||
<span>扩展信息</span>
|
||||
{list.length > 0 && <span className="tag">{list.length}</span>}
|
||||
<span className="cv">{open ? '收起' : '默认收起 · 全部非必填'}</span>
|
||||
</div>
|
||||
|
||||
{open && (
|
||||
<div className="extras-body">
|
||||
{list.length === 0 && (
|
||||
<div className="extras-empty">暂无条目,可记录仓库 / 分支 / 链接等任意信息</div>
|
||||
)}
|
||||
{list.map((e, i) => (
|
||||
<div className="extra-row" key={i}>
|
||||
<input
|
||||
className="inp extra-k"
|
||||
placeholder="名称"
|
||||
value={e.label || ''}
|
||||
disabled={readOnly}
|
||||
onChange={(ev) => update(i, 'label', ev.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="inp extra-v"
|
||||
placeholder="值"
|
||||
value={e.value || ''}
|
||||
disabled={readOnly}
|
||||
onChange={(ev) => update(i, 'value', ev.target.value)}
|
||||
/>
|
||||
{!readOnly && (
|
||||
<button type="button" className="extra-del" onClick={() => remove(i)}>×</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{!readOnly && (
|
||||
<button type="button" className="extras-add" onClick={add}>+ 添加一条</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import StatusDot from './StatusDot.jsx'
|
||||
const MAX_SUB = 3
|
||||
|
||||
export default function TaskCard({
|
||||
task, bindDrag, onClick, onToggleSub, onToggle, onAddSub, dragging = false, highlight = false,
|
||||
task, bindDrag, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false,
|
||||
}) {
|
||||
const children = task.children || []
|
||||
const shown = children.slice(0, MAX_SUB)
|
||||
@@ -38,6 +38,12 @@ export default function TaskCard({
|
||||
{task.members?.length > 0 && (
|
||||
<span className="bmembers">+ {task.members.map((m) => m.name).join('、')}</span>
|
||||
)}
|
||||
{onMore && (
|
||||
<span
|
||||
className="bmore-btn"
|
||||
onClick={(e) => { e.stopPropagation(); onMore(task) }}
|
||||
>⋯</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{shown.map((c) => (
|
||||
@@ -52,15 +58,6 @@ export default function TaskCard({
|
||||
</div>
|
||||
))}
|
||||
{rest > 0 && <div className="bmore">+{rest} 个子任务</div>}
|
||||
|
||||
{onAddSub && (
|
||||
<div
|
||||
className="bsub-add"
|
||||
onClick={(e) => { e.stopPropagation(); onAddSub(task) }}
|
||||
>
|
||||
+ 子任务
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user