feat(版本): 优化概览与只读状态
关键改动: - 增加需求排序和版本只读状态规则及测试 - 完善版本概览阶段耗时、项目页和工作台展示 - 优化小宝预警请求节流、建议状态和风险过滤 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -25,9 +25,10 @@ interface Props {
|
||||
versionId: string;
|
||||
requirementIds: string[];
|
||||
versionDeadline?: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props) {
|
||||
export function DevTaskTab({ versionId, requirementIds, versionDeadline, readOnly = false }: Props) {
|
||||
const { tasks, fetchTasks, deleteTask } = useDevTaskStore();
|
||||
const { categories, fetchCategories } = useTaskCategoryStore();
|
||||
const { fetchWorklogs } = useTaskWorklogStore();
|
||||
@@ -90,11 +91,13 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleSelect = (id: string) => {
|
||||
if (readOnly) return;
|
||||
const next = new Set(selectedIds);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
setSelectedIds(next);
|
||||
};
|
||||
const handleBatchDelete = () => {
|
||||
if (readOnly) return;
|
||||
if (selectedIds.size === 0) return;
|
||||
if (!confirm(`确定删除选中的 ${selectedIds.size} 个任务?`)) return;
|
||||
selectedIds.forEach((id) => deleteTask(id));
|
||||
@@ -174,21 +177,23 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
{hasFilter && <button onClick={() => { setFilterAssignee(''); setFilterStatus(''); setFilterBlocked(''); setFilterCategory(''); setKeyword(''); setPage(1); }} className="text-[10px] text-[var(--accent)] hover:underline shrink-0">清除</button>}
|
||||
|
||||
<div className="ml-auto flex items-center gap-2 shrink-0">
|
||||
{selectedIds.size > 0 && (
|
||||
{selectedIds.size > 0 && !readOnly && (
|
||||
<button onClick={handleBatchDelete} className="flex items-center gap-1 h-6 px-2 rounded text-[11px] font-medium bg-red-500 text-white hover:bg-red-600">
|
||||
<Trash2 className="h-3 w-3" />删除{selectedIds.size}项
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setShowCreate(true)} className="flex items-center gap-1 h-6 px-2.5 rounded text-[11px] font-medium bg-[var(--accent)] text-white hover:bg-[var(--accent-hover)]">
|
||||
<Plus className="h-3 w-3" />新建
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button onClick={() => setShowCreate(true)} className="flex items-center gap-1 h-6 px-2.5 rounded text-[11px] font-medium bg-[var(--accent)] text-white hover:bg-[var(--accent-hover)]">
|
||||
<Plus className="h-3 w-3" />新建
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{filteredTasks.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-[var(--line)] bg-[var(--bg-card)] p-12 text-center">
|
||||
<p className="text-[13px] text-[var(--ink-muted)]">{hasFilter ? '没有匹配的任务' : '暂无开发任务'}</p>
|
||||
{!hasFilter && <button onClick={() => setShowCreate(true)} className="mt-3 text-[12px] text-[var(--accent)] hover:underline">创建第一个任务</button>}
|
||||
{!hasFilter && !readOnly && <button onClick={() => setShowCreate(true)} className="mt-3 text-[12px] text-[var(--accent)] hover:underline">创建第一个任务</button>}
|
||||
</div>
|
||||
) : (
|
||||
Array.from(groupedByReq.entries()).map(([reqId, reqTasks]) => {
|
||||
@@ -197,9 +202,11 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
return (
|
||||
<div key={reqId} className="rounded-lg border border-[var(--line)] bg-[var(--bg-card)] overflow-hidden">
|
||||
<div className="flex items-center bg-[var(--bg-subtle)] border-b border-[var(--line)]">
|
||||
<div className="pl-4 flex items-center">
|
||||
<input type="checkbox" checked={reqTasks.every((t) => selectedIds.has(t.id))} onChange={() => { const ids = reqTasks.map((t) => t.id); const allSelected = ids.every((id) => selectedIds.has(id)); const next = new Set(selectedIds); if (allSelected) ids.forEach((id) => next.delete(id)); else ids.forEach((id) => next.add(id)); setSelectedIds(next); }} className="h-3.5 w-3.5 rounded border-[var(--line)]" />
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div className="pl-4 flex items-center">
|
||||
<input type="checkbox" checked={reqTasks.every((t) => selectedIds.has(t.id))} onChange={() => { const ids = reqTasks.map((t) => t.id); const allSelected = ids.every((id) => selectedIds.has(id)); const next = new Set(selectedIds); if (allSelected) ids.forEach((id) => next.delete(id)); else ids.forEach((id) => next.add(id)); setSelectedIds(next); }} className="h-3.5 w-3.5 rounded border-[var(--line)]" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-1 min-w-0 items-center gap-2 px-4 py-2">
|
||||
<span className="h-2 w-2 shrink-0" />
|
||||
<span className="w-16 shrink-0 truncate text-[11px] font-mono text-[var(--ink-muted)]" title={req?.code}>{req?.code}</span>
|
||||
@@ -209,9 +216,11 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
</div>
|
||||
{reqTasks.map((t) => (
|
||||
<div key={t.id} className="flex items-center">
|
||||
<div className="pl-4 flex items-center">
|
||||
<input type="checkbox" checked={selectedIds.has(t.id)} onChange={() => toggleSelect(t.id)} className="h-3.5 w-3.5 rounded border-[var(--line)]" onClick={(e) => e.stopPropagation()} />
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div className="pl-4 flex items-center">
|
||||
<input type="checkbox" checked={selectedIds.has(t.id)} onChange={() => toggleSelect(t.id)} className="h-3.5 w-3.5 rounded border-[var(--line)]" onClick={(e) => e.stopPropagation()} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<DevTaskRow task={t} category={categoryMap.get(t.categoryId)} categoryLabelWidthEm={categoryLabelWidthEm} onClick={() => setSelectedTaskId(t.id)} />
|
||||
</div>
|
||||
@@ -224,8 +233,8 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
|
||||
{total > 20 && <Pagination total={total} page={page} pageSize={pageSize} onChange={setPage} onPageSizeChange={setPageSize} />}
|
||||
|
||||
{showCreate && <DevTaskCreateModal versionId={versionId} requirementIds={requirementIds} versionDeadline={versionDeadline} onClose={() => setShowCreate(false)} />}
|
||||
{selectedTaskId && <DevTaskDetailDrawer taskId={selectedTaskId} allTaskIds={allTaskIds} onClose={() => setSelectedTaskId(null)} />}
|
||||
{showCreate && !readOnly && <DevTaskCreateModal versionId={versionId} requirementIds={requirementIds} versionDeadline={versionDeadline} onClose={() => setShowCreate(false)} />}
|
||||
{selectedTaskId && <DevTaskDetailDrawer taskId={selectedTaskId} allTaskIds={allTaskIds} readOnly={readOnly} onClose={() => setSelectedTaskId(null)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user