feat(v2.2): 完成高频读取热路径

This commit is contained in:
Script Generator
2026-07-03 11:53:25 +08:00
parent 5a90923031
commit 70460c3273
28 changed files with 3042 additions and 121 deletions

View File

@@ -34,10 +34,11 @@ import { calcBugSeverityRanking, calcPersonalEffortRanking, calcStageEffortMetri
import { addVersionMembers, DEFAULT_VERSION_MEMBER_ROLE, filterVersionMemberCandidates } from '@/lib/version-members';
import { addRecommendedVersionMembers, getDefaultRecommendedMemberNames, recommendVersionMembers, type MemberRecommendationGroup, type RecommendableRole } from '@/lib/member-recommendation';
import { getRequirementCoverageSummary } from '@/lib/version-plan';
import { buildVersionDataScope } from '@/lib/version-data-scope';
import { buildVersionDataScope, selectVersionDataScope, type VersionDataScope } from '@/lib/version-data-scope';
import { calcScopedVersionProgress } from '@/lib/version-progress';
import { canSubmitReleaseForm, getReleaseProgressWarning } from '@/lib/version-release';
import { isVersionReadonly } from '@/lib/version-status';
import { loadV22VersionDetailData } from '@/lib/v22-api';
import {
VERSION_DEVELOPMENT_TYPE_OPTIONS,
buildVersionName,
@@ -89,12 +90,41 @@ export default function VersionDetailPage() {
const router = useRouter();
const versionId = params.id as string;
const { overview, fetchOverview, updateVersion, deleteVersion } = useProductStore();
const { requirements, fetchRequirements, updateRequirement, createRequirement } = useRequirementStore();
const {
requirements,
loaded: requirementsLoaded,
fetchRequirements,
updateRequirement,
createRequirement,
} = useRequirementStore();
const { records, fetchRecords } = useOvertimeStore();
const { plans, fetchPlans, createPlan, updatePlan, completePlan, deletePlan } = useVersionPlanStore();
const { tasks: devTasks, fetchTasks: fetchDevTasks, deleteTask: deleteDevTask } = useDevTaskStore();
const { testCases, fetchTestCases, deleteTestCase } = useTestCaseStore();
const { bugs, fetchBugs, deleteBug } = useBugStore();
const {
plans,
loaded: plansLoaded,
fetchPlans,
createPlan,
updatePlan,
completePlan,
deletePlan,
} = useVersionPlanStore();
const {
tasks: devTasks,
loaded: devTasksLoaded,
fetchTasks: fetchDevTasks,
deleteTask: deleteDevTask,
} = useDevTaskStore();
const {
testCases,
loaded: testCasesLoaded,
fetchTestCases,
deleteTestCase,
} = useTestCaseStore();
const {
bugs,
loaded: bugsLoaded,
fetchBugs,
deleteBug,
} = useBugStore();
const user = useAuthStore((s) => s.user);
const { departments, members: allMembers, roles, fetchMembers } = useMemberStore();
const { categories: taskCategories, fetchCategories } = useTaskCategoryStore();
@@ -122,14 +152,63 @@ export default function VersionDetailPage() {
const [showEditModal, setShowEditModal] = useState(false);
const [showReleaseModal, setShowReleaseModal] = useState(false);
const [recommendationDataReady, setRecommendationDataReady] = useState(false);
const [v22Scope, setV22Scope] = useState<VersionDataScope | null>(null);
useEffect(() => { fetchOverview(); }, [fetchOverview]);
useEffect(() => { fetchRequirements(); }, [fetchRequirements]);
useEffect(() => { fetchRecords(); }, [fetchRecords]);
useEffect(() => { fetchPlans(); }, [fetchPlans]);
useEffect(() => { fetchDevTasks(); }, [fetchDevTasks]);
useEffect(() => { fetchTestCases(); }, [fetchTestCases]);
useEffect(() => { fetchBugs(); }, [fetchBugs]);
useEffect(() => {
let active = true;
setV22Scope(null);
loadV22VersionDetailData(versionId)
.then((data) => {
if (active) setV22Scope(data.scope);
})
.catch(() => {
if (active) setV22Scope(null);
});
return () => { active = false; };
}, [versionId]);
useEffect(() => {
if (activeTab === 'overview') {
fetchRecords();
return;
}
if (activeTab === 'requirements') {
fetchRequirements();
fetchDevTasks();
return;
}
if (activeTab === 'research' || activeTab === 'product' || activeTab === 'ui') {
fetchRequirements();
fetchPlans();
return;
}
if (activeTab === 'tasks') {
fetchRequirements();
fetchDevTasks();
return;
}
if (activeTab === 'testcases') {
fetchRequirements();
fetchDevTasks();
fetchTestCases();
fetchBugs();
return;
}
if (activeTab === 'bugs') {
fetchRequirements();
fetchTestCases();
fetchBugs();
}
}, [activeTab, fetchBugs, fetchDevTasks, fetchPlans, fetchRecords, fetchRequirements, fetchTestCases]);
useEffect(() => {
if (!showRecommendModal) return;
fetchRecords();
fetchRequirements();
fetchPlans();
fetchDevTasks();
fetchTestCases();
fetchBugs();
}, [fetchBugs, fetchDevTasks, fetchPlans, fetchRecords, fetchRequirements, fetchTestCases, showRecommendModal]);
useEffect(() => {
let active = true;
setRecommendationDataReady(false);
@@ -140,7 +219,7 @@ export default function VersionDetailPage() {
}, [fetchMembers, fetchCategories]);
const version = useMemo(() => getVersionDetail(overview, versionId), [overview, versionId]);
const versionScope = useMemo(
const appDataVersionScope = useMemo(
() => version
? buildVersionDataScope({
versionId: version.id,
@@ -154,6 +233,27 @@ export default function VersionDetailPage() {
: null,
[version, requirements, plans, devTasks, testCases, bugs, records],
);
const versionWriteStoresReady = requirementsLoaded && plansLoaded && devTasksLoaded && testCasesLoaded && bugsLoaded;
const requirementWriteReady = requirementsLoaded && devTasksLoaded;
const planWriteReady = requirementsLoaded && plansLoaded;
const devTaskWriteReady = requirementsLoaded && devTasksLoaded;
const testCaseWriteReady = requirementsLoaded && devTasksLoaded && testCasesLoaded && bugsLoaded;
const bugWriteReady = requirementsLoaded && testCasesLoaded && bugsLoaded;
const loadAllVersionStores = () => {
fetchRecords();
fetchRequirements();
fetchPlans();
fetchDevTasks();
fetchTestCases();
fetchBugs();
};
const versionScope = useMemo(
() => selectVersionDataScope({
appDataScope: appDataVersionScope,
v22Scope: versionWriteStoresReady ? null : v22Scope,
}),
[appDataVersionScope, v22Scope, versionWriteStoresReady],
);
const releaseProgress = versionScope
? calcScopedVersionProgress(versionScope.plans, versionScope.devTasks, versionScope.testCases)
: 0;
@@ -203,6 +303,15 @@ export default function VersionDetailPage() {
}
const scopedVersionData = versionScope!;
const appDataScopedVersionData = appDataVersionScope ?? scopedVersionData;
const displayRequirements = requirementsLoaded ? requirements : scopedVersionData.requirements;
const displayPlans = plansLoaded ? appDataScopedVersionData.plans : scopedVersionData.plans;
const displayDevTasks = devTasksLoaded ? appDataScopedVersionData.devTasks : scopedVersionData.devTasks;
const displayTestCases = testCasesLoaded ? appDataScopedVersionData.testCases : scopedVersionData.testCases;
const displayBugs = bugsLoaded ? appDataScopedVersionData.bugs : scopedVersionData.bugs;
const displayRequirementIds = requirementsLoaded
? appDataScopedVersionData.requirementIds
: scopedVersionData.requirementIds;
const memberRecommendationGroups = showRecommendModal && recommendationDataReady ? (() => {
const currentSystemParticipation = new Map<string, number>();
overview.forEach((product) => {
@@ -244,6 +353,11 @@ export default function VersionDetailPage() {
buttons.push({ label: '删除', action: () => {
if (confirm('确认删除该版本关联的需求会回到需求池版本下的计划、开发任务、测试用例、Bug 将被清除。')) {
// 释放关联需求
if (!versionWriteStoresReady) {
loadAllVersionStores();
alert('版本关联数据正在加载,请稍后再删除。');
return;
}
scopedVersionData.requirements.forEach((r) => updateRequirement(r.id, getRequirementVersionUnlinkPatch(r)));
// 清理计划任务
scopedVersionData.plans.forEach((p) => deletePlan(p.id));
@@ -866,13 +980,13 @@ export default function VersionDetailPage() {
<VersionRequirementsTab
versionId={version.id}
projectId={version.projectId}
requirements={requirements}
devTasks={devTasks}
requirements={displayRequirements}
devTasks={displayDevTasks}
versionMembers={version.members ?? []}
currentUserName={user?.name ?? ''}
readOnly={versionReadonly}
readOnly={versionReadonly || !requirementWriteReady}
onLink={(ids, addedBy) => {
if (versionReadonly) return;
if (versionReadonly || !requirementWriteReady) return;
ids.forEach((id) => {
const requirement = requirements.find((item) => item.id === id);
if (!requirement) return;
@@ -880,13 +994,13 @@ export default function VersionDetailPage() {
});
}}
onUnlink={(id) => {
if (versionReadonly) return;
if (versionReadonly || !requirementWriteReady) return;
const requirement = requirements.find((item) => item.id === id);
if (!requirement) return;
updateRequirement(id, getRequirementVersionUnlinkPatch(requirement));
}}
onCreateChange={(data) => {
if (versionReadonly) return;
if (versionReadonly || !requirementWriteReady) return;
createRequirement({
...data,
productId: version.productId,
@@ -908,10 +1022,10 @@ export default function VersionDetailPage() {
) : (activeTab === 'research' || activeTab === 'product' || activeTab === 'ui') ? (
(() => {
const pt = activeTab as 'research' | 'product' | 'ui';
const projectAdoptedReqs = getProjectAdoptedRequirementCandidates(requirements, version.projectId);
const projectAdoptedReqs = getProjectAdoptedRequirementCandidates(displayRequirements, version.projectId);
return (
<PlanTab
plans={scopedVersionData.plans}
plans={displayPlans}
versionId={version.id}
version={version}
versionDeadline={version.expectedReleaseDate ?? undefined}
@@ -919,10 +1033,10 @@ export default function VersionDetailPage() {
planType={pt}
versionMembers={version.members ?? []}
linkedRequirements={projectAdoptedReqs}
allRequirements={requirements}
readOnly={versionReadonly}
allRequirements={displayRequirements}
readOnly={versionReadonly || !planWriteReady}
onCreate={(data) => {
if (versionReadonly) return;
if (versionReadonly || !planWriteReady) return;
createPlan(data);
if ((pt === 'product') && data.linkedRequirementIds?.length) {
data.linkedRequirementIds.forEach((rid) => updateRequirement(rid, { productOwner: data.owner }));
@@ -935,18 +1049,18 @@ export default function VersionDetailPage() {
}
}}
onUpdate={(id, data) => {
if (versionReadonly) return;
if (versionReadonly || !planWriteReady) return;
updatePlan(id, data);
if ((pt === 'product') && data.linkedRequirementIds && data.owner) {
data.linkedRequirementIds.forEach((rid) => updateRequirement(rid, { productOwner: data.owner }));
}
}}
onComplete={(id, result) => {
if (versionReadonly) return;
if (versionReadonly || !planWriteReady) return;
return completePlan(id, result);
}}
onDelete={(id) => {
if (versionReadonly) return;
if (versionReadonly || !planWriteReady) return;
deletePlan(id);
}}
/>
@@ -955,29 +1069,29 @@ export default function VersionDetailPage() {
) : activeTab === 'tasks' ? (
<DevTaskTab
versionId={version.id}
requirementIds={scopedVersionData.requirementIds}
requirementIds={displayRequirementIds}
versionDeadline={version.expectedReleaseDate ?? undefined}
readOnly={versionReadonly}
versionTasks={scopedVersionData.devTasks}
versionRequirements={scopedVersionData.requirements}
readOnly={versionReadonly || !devTaskWriteReady}
versionTasks={displayDevTasks}
versionRequirements={displayRequirements}
/>
) : activeTab === 'testcases' ? (
<TestCaseTab
versionId={version.id}
requirementIds={scopedVersionData.requirementIds}
readOnly={versionReadonly}
versionCases={scopedVersionData.testCases}
versionBugs={scopedVersionData.bugs}
versionDevTasks={scopedVersionData.devTasks}
versionRequirements={scopedVersionData.requirements}
requirementIds={displayRequirementIds}
readOnly={versionReadonly || !testCaseWriteReady}
versionCases={displayTestCases}
versionBugs={displayBugs}
versionDevTasks={displayDevTasks}
versionRequirements={displayRequirements}
/>
) : activeTab === 'bugs' ? (
<BugTab
versionId={version.id}
requirementIds={scopedVersionData.requirementIds}
readOnly={versionReadonly}
versionBugs={scopedVersionData.bugs}
versionTestCases={scopedVersionData.testCases}
requirementIds={displayRequirementIds}
readOnly={versionReadonly || !bugWriteReady}
versionBugs={displayBugs}
versionTestCases={displayTestCases}
/>
) : (
<div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-12 flex items-center justify-center">