feat(v2.2): 完成高频读取热路径
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user