perf(web): 优化大数据量页面切换与聚合性能
This commit is contained in:
@@ -34,7 +34,8 @@ 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 { buildVersionProgressMap } from '@/lib/version-progress';
|
||||
import { buildVersionDataScope } 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 {
|
||||
@@ -139,21 +140,33 @@ export default function VersionDetailPage() {
|
||||
}, [fetchMembers, fetchCategories]);
|
||||
|
||||
const version = useMemo(() => getVersionDetail(overview, versionId), [overview, versionId]);
|
||||
const releaseProgressMap = useMemo<Record<string, number>>(
|
||||
() => version ? buildVersionProgressMap([version], plans, requirements, devTasks, testCases) : {},
|
||||
[version, plans, requirements, devTasks, testCases],
|
||||
const versionScope = useMemo(
|
||||
() => version
|
||||
? buildVersionDataScope({
|
||||
versionId: version.id,
|
||||
requirements,
|
||||
plans,
|
||||
devTasks,
|
||||
testCases,
|
||||
bugs,
|
||||
overtimeRecords: records,
|
||||
})
|
||||
: null,
|
||||
[version, requirements, plans, devTasks, testCases, bugs, records],
|
||||
);
|
||||
const releaseProgress = version ? (releaseProgressMap[version.id] ?? 0) : 0;
|
||||
const releaseProgress = versionScope
|
||||
? calcScopedVersionProgress(versionScope.plans, versionScope.devTasks, versionScope.testCases)
|
||||
: 0;
|
||||
const versionReadonly = version ? isVersionReadonly(version.status) : false;
|
||||
|
||||
// 自动同步版本状态:有计划开始时间<=今天,版本应进入对应阶段
|
||||
useEffect(() => {
|
||||
if (!version || !plans.length) return;
|
||||
if (!version || !versionScope || !versionScope.plans.length) return;
|
||||
if (version.status !== 'planned' && version.status !== 'developing') return;
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const stageMap = { research: 'requirement', product: 'product_design', ui: 'ui_design' } as const;
|
||||
const stageOrder: string[] = ['requirement', 'product_design', 'ui_design'];
|
||||
const versionPlans = plans.filter((p) => p.versionId === versionId && p.startTime <= today);
|
||||
const versionPlans = versionScope.plans.filter((p) => p.startTime <= today);
|
||||
if (versionPlans.length === 0) return;
|
||||
let targetStage = '';
|
||||
for (const p of versionPlans) {
|
||||
@@ -165,7 +178,7 @@ export default function VersionDetailPage() {
|
||||
if (version.status === 'planned' || (version.currentStage && stageOrder.indexOf(targetStage) > stageOrder.indexOf(version.currentStage))) {
|
||||
updateVersion(version.productId, version.id, { status: 'developing', currentStage: targetStage as any });
|
||||
}
|
||||
}, [plans, version, versionId, updateVersion]);
|
||||
}, [versionScope, version, updateVersion]);
|
||||
|
||||
if (!version) {
|
||||
return (
|
||||
@@ -189,36 +202,36 @@ export default function VersionDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const recommendationVersionReqs = requirements.filter((requirement) => requirement.versionId === version.id);
|
||||
const recommendationVersionReqIds = new Set(recommendationVersionReqs.map((requirement) => requirement.id));
|
||||
const recommendationVersionDevTasks = devTasks.filter((task) => recommendationVersionReqIds.has(task.requirementId));
|
||||
const recommendationVersionTestCases = testCases.filter((testCase) => testCase.versionId === version.id);
|
||||
const currentSystemParticipation = new Map<string, number>();
|
||||
overview.forEach((product) => {
|
||||
product.versions.forEach((item) => {
|
||||
const uniqueNames = new Set((item.members ?? []).map((member) => member.name));
|
||||
uniqueNames.forEach((name) => currentSystemParticipation.set(name, (currentSystemParticipation.get(name) ?? 0) + 1));
|
||||
const scopedVersionData = versionScope!;
|
||||
const memberRecommendationGroups = showRecommendModal && recommendationDataReady ? (() => {
|
||||
const currentSystemParticipation = new Map<string, number>();
|
||||
overview.forEach((product) => {
|
||||
product.versions.forEach((item) => {
|
||||
const uniqueNames = new Set((item.members ?? []).map((member) => member.name));
|
||||
uniqueNames.forEach((name) => currentSystemParticipation.set(name, (currentSystemParticipation.get(name) ?? 0) + 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
const memberRecommendationGroups = recommendVersionMembers({
|
||||
candidates: memberCandidates,
|
||||
currentMembers: version.members ?? [],
|
||||
devTasks,
|
||||
testCases,
|
||||
bugs,
|
||||
overtimeRecords: records,
|
||||
versionDeadline: version.expectedReleaseDate,
|
||||
scopeRequirements: recommendationVersionReqs,
|
||||
scopeDevTasks: recommendationVersionDevTasks,
|
||||
scopeTestCases: recommendationVersionTestCases,
|
||||
taskCategories,
|
||||
historicalStats: memberCandidates.flatMap((member) => {
|
||||
const projectParticipationCount = currentSystemParticipation.get(member.name);
|
||||
return typeof projectParticipationCount === 'number'
|
||||
? [{ name: member.name, projectParticipationCount }]
|
||||
: [];
|
||||
}),
|
||||
});
|
||||
|
||||
return recommendVersionMembers({
|
||||
candidates: memberCandidates,
|
||||
currentMembers: version.members ?? [],
|
||||
devTasks,
|
||||
testCases,
|
||||
bugs,
|
||||
overtimeRecords: records,
|
||||
versionDeadline: version.expectedReleaseDate,
|
||||
scopeRequirements: scopedVersionData.requirements,
|
||||
scopeDevTasks: scopedVersionData.devTasks,
|
||||
scopeTestCases: scopedVersionData.testCases,
|
||||
taskCategories,
|
||||
historicalStats: memberCandidates.flatMap((member) => {
|
||||
const projectParticipationCount = currentSystemParticipation.get(member.name);
|
||||
return typeof projectParticipationCount === 'number'
|
||||
? [{ name: member.name, projectParticipationCount }]
|
||||
: [];
|
||||
}),
|
||||
});
|
||||
})() : [];
|
||||
|
||||
const renderActions = () => {
|
||||
const buttons: { label: string; action: () => void; danger?: boolean; tone?: 'release'; icon?: JSX.Element }[] = [];
|
||||
@@ -231,15 +244,14 @@ export default function VersionDetailPage() {
|
||||
buttons.push({ label: '删除', action: () => {
|
||||
if (confirm('确认删除该版本?关联的需求会回到需求池,版本下的计划、开发任务、测试用例、Bug 将被清除。')) {
|
||||
// 释放关联需求
|
||||
requirements.filter((r) => r.versionId === version.id).forEach((r) => updateRequirement(r.id, getRequirementVersionUnlinkPatch(r)));
|
||||
scopedVersionData.requirements.forEach((r) => updateRequirement(r.id, getRequirementVersionUnlinkPatch(r)));
|
||||
// 清理计划任务
|
||||
plans.filter((p) => p.versionId === version.id).forEach((p) => deletePlan(p.id));
|
||||
scopedVersionData.plans.forEach((p) => deletePlan(p.id));
|
||||
// 清理开发任务
|
||||
const versionReqIds = new Set(requirements.filter((r) => r.versionId === version.id).map((r) => r.id));
|
||||
devTasks.filter((t) => versionReqIds.has(t.requirementId)).forEach((t) => deleteDevTask(t.id));
|
||||
scopedVersionData.devTasks.forEach((t) => deleteDevTask(t.id));
|
||||
// 清理测试用例和Bug
|
||||
testCases.filter((c) => c.versionId === version.id).forEach((c) => deleteTestCase(c.id));
|
||||
bugs.filter((b) => b.versionId === version.id).forEach((b) => deleteBug(b.id));
|
||||
scopedVersionData.testCases.forEach((c) => deleteTestCase(c.id));
|
||||
scopedVersionData.bugs.forEach((b) => deleteBug(b.id));
|
||||
deleteVersion(version.productId, version.id);
|
||||
router.push('/versions');
|
||||
}
|
||||
@@ -304,13 +316,12 @@ export default function VersionDetailPage() {
|
||||
{activeTab === 'overview' ? (
|
||||
(() => {
|
||||
const now = new Date();
|
||||
const versionReqs = requirements.filter((r) => r.versionId === version.id);
|
||||
const versionReqIds = new Set(versionReqs.map((r) => r.id));
|
||||
const versionPlans = plans.filter((p) => p.versionId === version.id);
|
||||
const versionDevTasks = devTasks.filter((t) => versionReqIds.has(t.requirementId));
|
||||
const versionTCs = testCases.filter((c) => c.versionId === version.id);
|
||||
const versionBugs = bugs.filter((b) => b.versionId === version.id);
|
||||
const versionOT = records.filter((r) => r.versionId === version.id);
|
||||
const versionReqs = scopedVersionData.requirements;
|
||||
const versionPlans = scopedVersionData.plans;
|
||||
const versionDevTasks = scopedVersionData.devTasks;
|
||||
const versionTCs = scopedVersionData.testCases;
|
||||
const versionBugs = scopedVersionData.bugs;
|
||||
const versionOT = scopedVersionData.overtimeRecords;
|
||||
const stageEffortMetrics = calcStageEffortMetrics({
|
||||
plans: versionPlans,
|
||||
devTasks: versionDevTasks,
|
||||
@@ -663,7 +674,7 @@ export default function VersionDetailPage() {
|
||||
|
||||
{/* 6. 需求变更统计 */}
|
||||
{(() => {
|
||||
const changeReqs = requirements.filter((r) => r.versionId === version.id && r.reqType === 'change');
|
||||
const changeReqs = versionReqs.filter((r) => r.reqType === 'change');
|
||||
if (changeReqs.length === 0) return null;
|
||||
|
||||
// 变更人员排名
|
||||
@@ -900,7 +911,7 @@ export default function VersionDetailPage() {
|
||||
const projectAdoptedReqs = getProjectAdoptedRequirementCandidates(requirements, version.projectId);
|
||||
return (
|
||||
<PlanTab
|
||||
plans={plans}
|
||||
plans={scopedVersionData.plans}
|
||||
versionId={version.id}
|
||||
version={version}
|
||||
versionDeadline={version.expectedReleaseDate ?? undefined}
|
||||
@@ -942,27 +953,32 @@ export default function VersionDetailPage() {
|
||||
);
|
||||
})()
|
||||
) : activeTab === 'tasks' ? (
|
||||
(() => {
|
||||
const versionReqs = requirements.filter((r) => r.versionId === version.id);
|
||||
return (
|
||||
<DevTaskTab
|
||||
versionId={version.id}
|
||||
requirementIds={versionReqs.map((r) => r.id)}
|
||||
versionDeadline={version.expectedReleaseDate ?? undefined}
|
||||
readOnly={versionReadonly}
|
||||
/>
|
||||
);
|
||||
})()
|
||||
<DevTaskTab
|
||||
versionId={version.id}
|
||||
requirementIds={scopedVersionData.requirementIds}
|
||||
versionDeadline={version.expectedReleaseDate ?? undefined}
|
||||
readOnly={versionReadonly}
|
||||
versionTasks={scopedVersionData.devTasks}
|
||||
versionRequirements={scopedVersionData.requirements}
|
||||
/>
|
||||
) : activeTab === 'testcases' ? (
|
||||
(() => {
|
||||
const versionReqs = requirements.filter((r) => r.versionId === version.id);
|
||||
return <TestCaseTab versionId={version.id} requirementIds={versionReqs.map((r) => r.id)} readOnly={versionReadonly} />;
|
||||
})()
|
||||
<TestCaseTab
|
||||
versionId={version.id}
|
||||
requirementIds={scopedVersionData.requirementIds}
|
||||
readOnly={versionReadonly}
|
||||
versionCases={scopedVersionData.testCases}
|
||||
versionBugs={scopedVersionData.bugs}
|
||||
versionDevTasks={scopedVersionData.devTasks}
|
||||
versionRequirements={scopedVersionData.requirements}
|
||||
/>
|
||||
) : activeTab === 'bugs' ? (
|
||||
(() => {
|
||||
const versionReqs = requirements.filter((r) => r.versionId === version.id);
|
||||
return <BugTab versionId={version.id} requirementIds={versionReqs.map((r) => r.id)} readOnly={versionReadonly} />;
|
||||
})()
|
||||
<BugTab
|
||||
versionId={version.id}
|
||||
requirementIds={scopedVersionData.requirementIds}
|
||||
readOnly={versionReadonly}
|
||||
versionBugs={scopedVersionData.bugs}
|
||||
versionTestCases={scopedVersionData.testCases}
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-12 flex items-center justify-center">
|
||||
<span className="text-[13px] text-[var(--ink-muted)]">功能开发中,敬请期待</span>
|
||||
|
||||
Reference in New Issue
Block a user