perf(web): 优化大数据量页面切换与聚合性能
This commit is contained in:
@@ -17,28 +17,36 @@ import { BUG_STATUS_LABEL, BUG_SEVERITY_LABEL, aggregateBugActualHours, bugInter
|
||||
import { calcTwoMetrics } from '@/lib/work-hours';
|
||||
import { isMemberReference, resolveMemberDisplayName } from '@/lib/member-system';
|
||||
import { buildEffortSummaryMetrics, sumPositiveHours } from '@/lib/effort-summary';
|
||||
import type { BugStatus, BugSeverity } from '@/lib/bug';
|
||||
import type { Bug, BugStatus, BugSeverity } from '@/lib/bug';
|
||||
import type { TestCase } from '@/lib/test-case';
|
||||
|
||||
interface Props {
|
||||
versionId: string;
|
||||
requirementIds: string[];
|
||||
readOnly?: boolean;
|
||||
versionBugs?: Bug[];
|
||||
versionTestCases?: TestCase[];
|
||||
}
|
||||
|
||||
export function BugTab({ versionId, requirementIds, readOnly = false }: Props) {
|
||||
export function BugTab({ versionId, requirementIds, readOnly = false, versionBugs: scopedVersionBugs, versionTestCases }: Props) {
|
||||
const { bugs, fetchBugs } = useBugStore();
|
||||
const { testCases, fetchTestCases } = useTestCaseStore();
|
||||
const { requirements } = useRequirementStore();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const members = useMemberStore((s) => s.members);
|
||||
|
||||
useEffect(() => { fetchBugs(); }, [fetchBugs]);
|
||||
useEffect(() => { fetchTestCases(); }, [fetchTestCases]);
|
||||
useEffect(() => {
|
||||
if (!scopedVersionBugs) fetchBugs();
|
||||
}, [fetchBugs, scopedVersionBugs]);
|
||||
useEffect(() => {
|
||||
if (!versionTestCases) fetchTestCases();
|
||||
}, [fetchTestCases, versionTestCases]);
|
||||
|
||||
const versionBugs = useMemo(
|
||||
const fallbackVersionBugs = useMemo(
|
||||
() => bugs.filter((b) => b.versionId === versionId),
|
||||
[bugs, versionId],
|
||||
);
|
||||
const versionBugs = scopedVersionBugs ?? fallbackVersionBugs;
|
||||
|
||||
const openCount = versionBugs.filter((b) => b.status === 'open').length;
|
||||
const fixingCount = versionBugs.filter((b) => b.status === 'fixing').length;
|
||||
@@ -66,7 +74,8 @@ export function BugTab({ versionId, requirementIds, readOnly = false }: Props) {
|
||||
|
||||
const [selectedBugId, setSelectedBugId] = useState<string | null>(null);
|
||||
|
||||
const testCaseMap = useMemo(() => new Map(testCases.map((c) => [c.id, c])), [testCases]);
|
||||
const visibleTestCases = versionTestCases ?? testCases;
|
||||
const testCaseMap = useMemo(() => new Map(visibleTestCases.map((c) => [c.id, c])), [visibleTestCases]);
|
||||
const currentMember = useMemo(() => members.find((m) => m.id === user?.id), [members, user?.id]);
|
||||
const currentUserName = currentMember?.name ?? user?.name;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useState, useMemo, useEffect } from 'react';
|
||||
import { Plus, Code2, Trash2 } from 'lucide-react';
|
||||
import { useDevTaskStore } from '@/stores/useDevTaskStore';
|
||||
import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore';
|
||||
import { useTaskWorklogStore } from '@/stores/useTaskWorklogStore';
|
||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useMemberStore } from '@/stores/useMemberStore';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
@@ -20,12 +19,15 @@ import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { orderByRequirementForGrouping } from '@/lib/requirement-grouping';
|
||||
import { buildEffortSummaryMetrics, sumPositiveHours } from '@/lib/effort-summary';
|
||||
import type { DevTask, DevTaskStatus } from '@/lib/dev-task';
|
||||
import type { Requirement } from '@/lib/requirement';
|
||||
|
||||
interface Props {
|
||||
versionId: string;
|
||||
requirementIds: string[];
|
||||
versionDeadline?: string;
|
||||
readOnly?: boolean;
|
||||
versionTasks?: DevTask[];
|
||||
versionRequirements?: Requirement[];
|
||||
}
|
||||
|
||||
const NO_REQUIREMENT_GROUP_PREFIX = '__no_requirement__';
|
||||
@@ -38,23 +40,24 @@ function getNoRequirementGroupName(groupKey: string): string {
|
||||
return groupKey.slice(NO_REQUIREMENT_GROUP_PREFIX.length) || '未命名需求';
|
||||
}
|
||||
|
||||
export function DevTaskTab({ versionId, requirementIds, versionDeadline, readOnly = false }: Props) {
|
||||
export function DevTaskTab({ versionId, requirementIds, versionDeadline, readOnly = false, versionTasks: scopedVersionTasks, versionRequirements }: Props) {
|
||||
const { tasks, fetchTasks, deleteTask } = useDevTaskStore();
|
||||
const { categories, fetchCategories } = useTaskCategoryStore();
|
||||
const { fetchWorklogs } = useTaskWorklogStore();
|
||||
const { requirements } = useRequirementStore();
|
||||
const { members } = useMemberStore();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
|
||||
useEffect(() => { fetchTasks(); }, [fetchTasks]);
|
||||
useEffect(() => {
|
||||
if (!scopedVersionTasks) fetchTasks();
|
||||
}, [fetchTasks, scopedVersionTasks]);
|
||||
useEffect(() => { fetchCategories(); }, [fetchCategories]);
|
||||
useEffect(() => { fetchWorklogs(); }, [fetchWorklogs]);
|
||||
|
||||
const reqIdSet = useMemo(() => new Set(requirementIds), [requirementIds]);
|
||||
const versionTasks = useMemo(
|
||||
const fallbackVersionTasks = useMemo(
|
||||
() => tasks.filter((t) => t.versionId === versionId || (!t.versionId && reqIdSet.has(t.requirementId))),
|
||||
[tasks, reqIdSet, versionId],
|
||||
);
|
||||
const versionTasks = scopedVersionTasks ?? fallbackVersionTasks;
|
||||
|
||||
const [filterAssignee, setFilterAssignee] = useState('');
|
||||
const [filterStatus, setFilterStatus] = useState('');
|
||||
@@ -130,7 +133,8 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline, readOnl
|
||||
() => Math.max(4, ...categories.map((category) => category.name.length)) + 1,
|
||||
[categories],
|
||||
);
|
||||
const requirementMap = useMemo(() => new Map(requirements.map((r) => [r.id, r])), [requirements]);
|
||||
const visibleRequirements = versionRequirements ?? requirements;
|
||||
const requirementMap = useMemo(() => new Map(visibleRequirements.map((r) => [r.id, r])), [visibleRequirements]);
|
||||
const allTaskIds = useMemo(() => versionTasks.map((t) => t.id), [versionTasks]);
|
||||
|
||||
const assignees = useMemo(() => Array.from(new Set(versionTasks.map((t) => t.assigneeId))), [versionTasks]);
|
||||
|
||||
@@ -20,12 +20,20 @@ import { SearchInput, matchTitleOrNo } from '@/components/SearchInput';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { orderByRequirementForGrouping } from '@/lib/requirement-grouping';
|
||||
import { buildEffortSummaryMetrics, sumPositiveHours } from '@/lib/effort-summary';
|
||||
import type { Bug } from '@/lib/bug';
|
||||
import type { DevTask } from '@/lib/dev-task';
|
||||
import type { Requirement } from '@/lib/requirement';
|
||||
import type { TestCase } from '@/lib/test-case';
|
||||
import type { TestCaseStatus } from '@/lib/test-case';
|
||||
|
||||
interface Props {
|
||||
versionId: string;
|
||||
requirementIds: string[];
|
||||
readOnly?: boolean;
|
||||
versionCases?: TestCase[];
|
||||
versionBugs?: Bug[];
|
||||
versionDevTasks?: DevTask[];
|
||||
versionRequirements?: Requirement[];
|
||||
}
|
||||
|
||||
const NO_REQUIREMENT_GROUP_PREFIX = '__no_requirement__';
|
||||
@@ -38,7 +46,15 @@ function getNoRequirementGroupName(groupKey: string): string {
|
||||
return groupKey.slice(NO_REQUIREMENT_GROUP_PREFIX.length) || '未命名需求';
|
||||
}
|
||||
|
||||
export function TestCaseTab({ versionId, requirementIds, readOnly = false }: Props) {
|
||||
export function TestCaseTab({
|
||||
versionId,
|
||||
requirementIds,
|
||||
readOnly = false,
|
||||
versionCases: scopedVersionCases,
|
||||
versionBugs: scopedVersionBugs,
|
||||
versionDevTasks: scopedVersionDevTasks,
|
||||
versionRequirements,
|
||||
}: Props) {
|
||||
const { testCases, fetchTestCases, createTestCases, deleteTestCase } = useTestCaseStore();
|
||||
const { bugs, fetchBugs } = useBugStore();
|
||||
const { tasks: devTasks } = useDevTaskStore();
|
||||
@@ -46,15 +62,20 @@ export function TestCaseTab({ versionId, requirementIds, readOnly = false }: Pro
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const { categories, fetchCategories } = useTaskCategoryStore();
|
||||
|
||||
useEffect(() => { fetchTestCases(); }, [fetchTestCases]);
|
||||
useEffect(() => { fetchBugs(); }, [fetchBugs]);
|
||||
useEffect(() => {
|
||||
if (!scopedVersionCases) fetchTestCases();
|
||||
}, [fetchTestCases, scopedVersionCases]);
|
||||
useEffect(() => {
|
||||
if (!scopedVersionBugs) fetchBugs();
|
||||
}, [fetchBugs, scopedVersionBugs]);
|
||||
useEffect(() => { fetchCategories(); }, [fetchCategories]);
|
||||
|
||||
const reqIdSet = useMemo(() => new Set(requirementIds), [requirementIds]);
|
||||
const versionCases = useMemo(
|
||||
const fallbackVersionCases = useMemo(
|
||||
() => testCases.filter((c) => c.versionId === versionId),
|
||||
[testCases, versionId],
|
||||
);
|
||||
const versionCases = scopedVersionCases ?? fallbackVersionCases;
|
||||
const rounds = useMemo(
|
||||
() => Array.from(new Set(versionCases.map((testCase) => getTestCaseRoundNo(testCase)))).sort((a, b) => a - b),
|
||||
[versionCases],
|
||||
@@ -75,15 +96,17 @@ export function TestCaseTab({ versionId, requirementIds, readOnly = false }: Pro
|
||||
() => versionCases.filter((testCase) => getTestCaseRoundNo(testCase) === 1),
|
||||
[versionCases],
|
||||
);
|
||||
const versionBugs = useMemo(
|
||||
const fallbackVersionBugs = useMemo(
|
||||
() => bugs.filter((bug) => bug.versionId === versionId),
|
||||
[bugs, versionId],
|
||||
);
|
||||
const versionBugs = scopedVersionBugs ?? fallbackVersionBugs;
|
||||
|
||||
const versionDevTasks = useMemo(
|
||||
const fallbackVersionDevTasks = useMemo(
|
||||
() => devTasks.filter((t) => t.versionId === versionId || (!t.versionId && reqIdSet.has(t.requirementId))),
|
||||
[devTasks, reqIdSet, versionId],
|
||||
);
|
||||
const versionDevTasks = scopedVersionDevTasks ?? fallbackVersionDevTasks;
|
||||
|
||||
const stats = calcTestProgress(activeRoundCases, versionBugs);
|
||||
const { actual: tcActualHours } = aggregateTestCaseHours(versionCases);
|
||||
@@ -168,7 +191,8 @@ export function TestCaseTab({ versionId, requirementIds, readOnly = false }: Pro
|
||||
return map;
|
||||
}, [paged]);
|
||||
|
||||
const requirementMap = useMemo(() => new Map(requirements.map((r) => [r.id, r])), [requirements]);
|
||||
const visibleRequirements = versionRequirements ?? requirements;
|
||||
const requirementMap = useMemo(() => new Map(visibleRequirements.map((r) => [r.id, r])), [visibleRequirements]);
|
||||
const categoryMap = useMemo(() => new Map(categories.map((c) => [c.id, c])), [categories]);
|
||||
const categoryLabelWidthEm = useMemo(
|
||||
() => Math.max(4, ...categories.map((category) => category.name.length)) + 1,
|
||||
|
||||
Reference in New Issue
Block a user