fix(data): 清理旧数据并保护服务端写入

This commit is contained in:
Script Generator
2026-07-02 09:30:50 +08:00
parent 28e0c6de19
commit 916bd17a48
38 changed files with 1346 additions and 194 deletions

View File

@@ -11,7 +11,7 @@ import { flattenProjects, flattenVersions } from '@/lib/derive';
import { REQ_STATUS_LABEL, REQ_STATUS_COLOR, SOURCE_TYPE_LABEL } from '@/lib/requirement';
import type { Requirement, RequirementStatus, SourceType } from '@/lib/requirement';
import { deriveReqDevStatus, canEditRequirement, canCloseRequirement, REQ_DEV_STATUS_LABEL, REQ_DEV_STATUS_COLOR } from '@/lib/linkage-engine';
import { buildRequirementScopeTree, filterRequirementsByScope, type RequirementProductScopeNode, type RequirementScopeSelection } from '@/lib/requirement-scope';
import { buildRequirementScopeTree, filterRequirementScopeTreeByKeyword, filterRequirementsByScope, type RequirementProductScopeNode, type RequirementScopeSelection } from '@/lib/requirement-scope';
import { sortRequirementsByCreatedAt, type RequirementDateSort } from '@/lib/requirement-sort';
import { Pagination, usePagination } from '@/components/Pagination';
import { RequirementModal } from '@/components/requirement/RequirementModal';
@@ -173,6 +173,7 @@ function RequirementsPageContent() {
const allVersions = useMemo(() => flattenVersions(overview), [overview]);
const [selectedScope, setSelectedScope] = useState<RequirementScopeSelection>({ type: 'all' });
const [scopeSearch, setScopeSearch] = useState('');
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [priorityFilter, setPriorityFilter] = useState('all');
@@ -202,6 +203,7 @@ function RequirementsPageContent() {
}, [selectedScopeKey]);
const scopeTree = useMemo(() => buildRequirementScopeTree(overview, requirements), [overview, requirements]);
const searchedScopeTree = useMemo(() => filterRequirementScopeTreeByKeyword(scopeTree, scopeSearch), [scopeTree, scopeSearch]);
const scopedRequirements = useMemo(() => filterRequirementsByScope(requirements, selectedScope), [requirements, selectedScope]);
const scopedVersions = useMemo(() => {
if (selectedScope.type === 'product') return allVersions.filter((v) => v.productId === selectedScope.productId);
@@ -303,6 +305,18 @@ function RequirementsPageContent() {
{requirements.length}
</span>
</div>
<div className="shrink-0 border-b border-[var(--line)] px-3 py-3">
<div className="relative">
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--ink-muted)]" strokeWidth={2} />
<input
value={scopeSearch}
onChange={(event) => setScopeSearch(event.target.value)}
placeholder="搜索产品或项目"
aria-label="搜索产品或项目"
className="h-8 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] pl-8 pr-3 text-[12px] text-[var(--ink)] placeholder:text-[var(--ink-muted)] focus:border-[var(--accent)] focus:outline-none focus:ring-2 focus:ring-[var(--accent-ring)]"
/>
</div>
</div>
<div className="flex-1 overflow-y-auto p-2">
<button
onClick={() => setSelectedScope({ type: 'all' })}
@@ -316,7 +330,7 @@ function RequirementsPageContent() {
<span className="flex-1 text-left"></span>
<ScopeCount count={requirements.length} />
</button>
{scopeTree.map((product) => (
{searchedScopeTree.map((product) => (
<RequirementProductNode
key={product.id}
product={product}
@@ -324,6 +338,11 @@ function RequirementsPageContent() {
onSelect={setSelectedScope}
/>
))}
{searchedScopeTree.length === 0 && scopeSearch.trim() && (
<div className="mt-3 rounded-lg border border-dashed border-[var(--line)] px-3 py-4 text-center text-[12px] text-[var(--ink-muted)]">
</div>
)}
</div>
</aside>