feat(v2.4): 切换需求池领域主写

This commit is contained in:
2026-07-08 12:18:15 +08:00
parent 142800f75b
commit 5f5a211c99
11 changed files with 602 additions and 56 deletions

View File

@@ -432,7 +432,6 @@ function RequirementsPageContent() {
}, [scopedRequirements, search, statusFilter, priorityFilter, typeFilter, versionFilter, dateSort, requirementDevStatusMap]);
const appDataRequirementById = useMemo(() => new Map(requirements.map((item) => [item.id, item])), [requirements]);
const appDataRequirementIds = useMemo(() => new Set(appDataRequirementById.keys()), [appDataRequirementById]);
const usingV22Requirements = Boolean(v22RequirementQuery) && !v22RequirementsFailed;
const appTotalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
const appPage = Math.min(page, appTotalPages);
@@ -450,7 +449,7 @@ function RequirementsPageContent() {
: filtered.length;
const displayPage = usingV22Requirements ? page : appPage;
const displayedScopeCount = usingV22Requirements ? total : scopedRequirements.length;
const canMutateRequirement = (req: Requirement) => requirementsLoaded && appDataRequirementIds.has(req.id);
const canMutateRequirement = (req: Requirement) => Boolean(req.productId);
const handlePageChange = (nextPage: number) => {
if (usingV22Requirements && nextPage > page && !v22CursorByPage[nextPage]) return;
setPage(nextPage);
@@ -789,7 +788,7 @@ function RequirementsPageContent() {
{canMutateRequirement(req) && req.status === 'pending_review' && (
<>
<button
onClick={() => updateRequirement(req.id, { status: 'adopted' })}
onClick={() => updateRequirement(req.id, { productId: req.productId, status: 'adopted' })}
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.success}`}
title="采纳"
aria-label="采纳需求"
@@ -809,7 +808,7 @@ function RequirementsPageContent() {
{/* 已采纳/已规划:关闭 */}
{canMutateRequirement(req) && (req.status === 'adopted' || req.status === 'planned') && canCloseRequirement(req.id, devTasksByRequirement.get(req.id) ?? []) && (
<button
onClick={() => updateRequirement(req.id, { status: 'closed' })}
onClick={() => updateRequirement(req.id, { productId: req.productId, status: 'closed' })}
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.warning}`}
title="关闭"
aria-label="关闭需求"
@@ -820,7 +819,7 @@ function RequirementsPageContent() {
{/* 删除:待评审/已拒绝/已关闭(已采纳后不能直接删,需走拒绝) */}
{canMutateRequirement(req) && (['pending_review', 'rejected', 'closed'] as const).includes(req.status as any) && (
<button
onClick={() => deleteRequirement(req.id)}
onClick={() => deleteRequirement(req.id, req.productId)}
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.danger}`}
title="删除"
aria-label="删除需求"
@@ -854,10 +853,10 @@ function RequirementsPageContent() {
resolveVersionName={resolveVersionName}
onClose={() => setViewingReq(null)}
onEdit={canMutateRequirement(viewingReq) && canEditRequirement(viewingReq.id, devTasksByRequirement.get(viewingReq.id) ?? []) ? () => handleEdit(viewingReq) : undefined}
onAdopt={canMutateRequirement(viewingReq) ? () => { updateRequirement(viewingReq.id, { status: 'adopted' }); setViewingReq(null); } : undefined}
onAdopt={canMutateRequirement(viewingReq) ? () => { updateRequirement(viewingReq.id, { productId: viewingReq.productId, status: 'adopted' }); setViewingReq(null); } : undefined}
onReject={canMutateRequirement(viewingReq) ? () => { setRejectingReq(viewingReq); setRejectReason(''); setViewingReq(null); } : undefined}
onCloseReq={canMutateRequirement(viewingReq) && canCloseRequirement(viewingReq.id, devTasksByRequirement.get(viewingReq.id) ?? []) ? () => { updateRequirement(viewingReq.id, { status: 'closed' }); setViewingReq(null); } : undefined}
onDelete={canMutateRequirement(viewingReq) ? () => { deleteRequirement(viewingReq.id); setViewingReq(null); } : undefined}
onCloseReq={canMutateRequirement(viewingReq) && canCloseRequirement(viewingReq.id, devTasksByRequirement.get(viewingReq.id) ?? []) ? () => { updateRequirement(viewingReq.id, { productId: viewingReq.productId, status: 'closed' }); setViewingReq(null); } : undefined}
onDelete={canMutateRequirement(viewingReq) ? () => { deleteRequirement(viewingReq.id, viewingReq.productId); setViewingReq(null); } : undefined}
/>
)}
@@ -882,7 +881,7 @@ function RequirementsPageContent() {
data.versionId,
data.versionId !== editingReq.versionId ? currentUserName : editingReq.addedToVersionBy,
);
updateRequirement(editingReq.id, { ...data, ...versionPatch });
updateRequirement(editingReq.id, { ...data, productId: editingReq.productId, ...versionPatch });
} else {
createRequirement(data);
}
@@ -947,7 +946,7 @@ function RequirementsPageContent() {
<button
onClick={() => {
if (!rejectReason.trim()) return;
updateRequirement(rejectingReq.id, { status: 'rejected', description: rejectingReq.description + `\n\n【拒绝原因】${rejectReason.trim()}` });
updateRequirement(rejectingReq.id, { productId: rejectingReq.productId, status: 'rejected', description: rejectingReq.description + `\n\n【拒绝原因】${rejectReason.trim()}` });
setRejectingReq(null);
}}
disabled={!rejectReason.trim()}

View File

@@ -358,7 +358,10 @@ export default function VersionDetailPage() {
alert('版本关联数据正在加载,请稍后再删除。');
return;
}
scopedVersionData.requirements.forEach((r) => updateRequirement(r.id, getRequirementVersionUnlinkPatch(r)));
scopedVersionData.requirements.forEach((r) => updateRequirement(r.id, {
productId: r.productId,
...getRequirementVersionUnlinkPatch(r),
}));
// 清理计划任务
scopedVersionData.plans.forEach((p) => deletePlan(p.id));
// 清理开发任务
@@ -988,16 +991,22 @@ export default function VersionDetailPage() {
onLink={(ids, addedBy) => {
if (versionReadonly || !requirementWriteReady) return;
ids.forEach((id) => {
const requirement = requirements.find((item) => item.id === id);
const requirement = displayRequirements.find((item) => item.id === id);
if (!requirement) return;
updateRequirement(id, getRequirementVersionSelectionPatch(requirement, version.id, addedBy));
updateRequirement(id, {
productId: requirement.productId,
...getRequirementVersionSelectionPatch(requirement, version.id, addedBy),
});
});
}}
onUnlink={(id) => {
if (versionReadonly || !requirementWriteReady) return;
const requirement = requirements.find((item) => item.id === id);
const requirement = displayRequirements.find((item) => item.id === id);
if (!requirement) return;
updateRequirement(id, getRequirementVersionUnlinkPatch(requirement));
updateRequirement(id, {
productId: requirement.productId,
...getRequirementVersionUnlinkPatch(requirement),
});
}}
onCreateChange={(data) => {
if (versionReadonly || !requirementWriteReady) return;
@@ -1039,7 +1048,10 @@ export default function VersionDetailPage() {
if (versionReadonly || !planWriteReady) return;
createPlan(data);
if ((pt === 'product') && data.linkedRequirementIds?.length) {
data.linkedRequirementIds.forEach((rid) => updateRequirement(rid, { productOwner: data.owner }));
data.linkedRequirementIds.forEach((rid) => {
const requirement = displayRequirements.find((item) => item.id === rid);
if (requirement) updateRequirement(rid, { productId: requirement.productId, productOwner: data.owner });
});
}
// 同步版本状态:计划开始时间<=今天,版本进入对应阶段
const today = new Date().toISOString().slice(0, 10);
@@ -1052,7 +1064,10 @@ export default function VersionDetailPage() {
if (versionReadonly || !planWriteReady) return;
updatePlan(id, data);
if ((pt === 'product') && data.linkedRequirementIds && data.owner) {
data.linkedRequirementIds.forEach((rid) => updateRequirement(rid, { productOwner: data.owner }));
data.linkedRequirementIds.forEach((rid) => {
const requirement = displayRequirements.find((item) => item.id === rid);
if (requirement) updateRequirement(rid, { productId: requirement.productId, productOwner: data.owner });
});
}
}}
onComplete={(id, result) => {

View File

@@ -159,7 +159,9 @@ function VersionsPageContent() {
setOpenMenuId(null);
if (action === 'delete') {
if (!confirm('确认删除该版本?关联的需求会回到需求池。')) return;
requirements.filter((r) => r.versionId === version.id).forEach((r) => updateRequirement(r.id, getRequirementVersionUnlinkPatch(r)));
requirements
.filter((r) => r.versionId === version.id)
.forEach((r) => updateRequirement(r.id, { productId: r.productId, ...getRequirementVersionUnlinkPatch(r) }));
deleteVersion(version.productId, version.id);
return;
}