feat(平台): 补齐服务端持久化和AI拆解契约
This commit is contained in:
@@ -6,6 +6,7 @@ import type { Requirement, ChangeReason } from '@/lib/requirement';
|
||||
import type { DevTask } from '@/lib/dev-task';
|
||||
import { REQ_STATUS_LABEL, REQ_STATUS_COLOR, CHANGE_REASON_LABEL } from '@/lib/requirement';
|
||||
import { deriveReqDevStatus, canEditRequirement, REQ_DEV_STATUS_LABEL, REQ_DEV_STATUS_COLOR } from '@/lib/linkage-engine';
|
||||
import { getProjectAdoptedRequirementCandidates } from '@/lib/requirement-selector';
|
||||
|
||||
interface Props {
|
||||
versionId: string;
|
||||
@@ -23,7 +24,7 @@ export function VersionRequirementsTab({ versionId, projectId, requirements, dev
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [showChangeModal, setShowChangeModal] = useState(false);
|
||||
const linkedReqs = requirements.filter((r) => r.versionId === versionId);
|
||||
const availableReqs = requirements.filter((r) => r.projectId === projectId && !r.versionId && r.status === 'adopted');
|
||||
const availableReqs = getProjectAdoptedRequirementCandidates(requirements, projectId).filter((r) => !r.versionId);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -43,7 +44,7 @@ export function VersionRequirementsTab({ versionId, projectId, requirements, dev
|
||||
|
||||
{linkedReqs.length === 0 ? (
|
||||
<div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-12 text-center text-[13px] text-[var(--ink-muted)]">
|
||||
暂无关联需求,从需求池中添加
|
||||
暂无关联需求,从当前项目已采纳需求中添加
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] overflow-hidden">
|
||||
@@ -213,7 +214,7 @@ function AddRequirementModal({ available, onClose, onConfirm }: {
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40" onClick={onClose}>
|
||||
<div className="w-full max-w-lg rounded-2xl bg-[var(--bg-card)] border border-[var(--line)] shadow-[var(--shadow-md)] flex flex-col max-h-[70vh]" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-[var(--line)]">
|
||||
<h3 className="text-[13px] font-semibold text-[var(--ink)]">从需求池添加</h3>
|
||||
<h3 className="text-[13px] font-semibold text-[var(--ink)]">从项目已采纳需求添加</h3>
|
||||
<button onClick={onClose} className="p-1 rounded hover:bg-[var(--bg-subtle)] text-[var(--ink-muted)]"><X className="h-4 w-4" /></button>
|
||||
</div>
|
||||
<div className="px-5 py-3 border-b border-[var(--line)]">
|
||||
@@ -221,6 +222,15 @@ function AddRequirementModal({ available, onClose, onConfirm }: {
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[var(--ink-muted)]" />
|
||||
<input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="搜索需求编号或标题" className="h-8 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] pl-9 pr-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
|
||||
</div>
|
||||
{filtered.length > 0 && (
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<span className="text-[11px] text-[var(--ink-muted)]">当前筛选 {filtered.length} 条,已选 {selected.size} 条</span>
|
||||
<div className="flex gap-2">
|
||||
<button type="button" onClick={() => setSelected(new Set(filtered.map((r) => r.id)))} className="text-[11px] text-[var(--accent)] hover:underline">全选</button>
|
||||
<button type="button" onClick={() => setSelected(new Set())} className="text-[11px] text-[var(--ink-muted)] hover:underline">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto px-5 py-3">
|
||||
{filtered.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user