build, fix, feat, refactor: 优化长列表性能,新增注册表并发校验,升级v5.1.0
CI / 前端检查 (TypeScript + Lint + Test) (push) Has been cancelled
CI / Rust 检查 (Check + Clippy + Test) (push) Has been cancelled

- 前端引入@tanstack/react-virtual虚拟列表库,重构PathTable与MergePreview组件,优化大量路径条目下的渲染性能
- 为后端注册表保存接口添加原始路径比对逻辑,防止并发修改导致的配置覆盖,同步更新前端保存逻辑传递原始路径参数
- 替换core模块手动编写的Windows API FFI声明为windows-sys官方库,简化代码维护
- 完善单元测试,新增空数组处理、边界场景的测试用例
- 更新项目依赖与锁定文件,将版本升级至v5.1.0
- 新增项目代码架构审查文档
This commit is contained in:
2026-05-31 15:16:05 +08:00
parent a9b36a6f47
commit 60de924b08
13 changed files with 315 additions and 137 deletions
+83 -67
View File
@@ -1,9 +1,10 @@
import { useMemo, useCallback } from 'react';
import { useMemo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useAppStore } from '@/store/app-store';
import { TargetType } from '@/core/undo-redo';
import { usePathValidation } from '@/hooks/use-path-validation';
import type { ValidationState } from '@/hooks/use-path-validation';
import { useVirtualizer } from '@tanstack/react-virtual';
interface PathTableProps {
tabId: 'system' | 'user';
@@ -80,76 +81,91 @@ export function PathTable({ tabId }: PathTableProps) {
[isActive, paths],
);
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: filtered.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 28, // 预估行高 28px
initialRect: { width: 800, height: 600 },
});
return (
<div className="flex-1 overflow-auto">
<table className="w-full border-collapse">
<thead>
<tr
className="sticky top-0 z-10 text-left text-xs uppercase"
style={{ backgroundColor: 'var(--app-list-alt)', color: 'var(--app-fg)' }}
>
<th className="w-8 px-2 py-1">#</th>
<th className="w-6 px-1 py-1"></th>
<th className="px-2 py-1">{t('table.path')}</th>
</tr>
</thead>
<tbody>
{filtered.map(({ path, index, enabled }, rowIdx) => {
const v = validations[rowIdx];
const isSelected = selectedIndices.includes(index);
let textColor = 'var(--app-fg)';
if (v.state === 'invalid') textColor = '#dc3545';
else if (v.isDuplicate) textColor = '#fd7e14';
else if (v.state === 'unknown') textColor = 'var(--app-fg)';
<div ref={parentRef} className="flex-1 overflow-auto relative">
<div
className="sticky top-0 z-10 flex text-left text-xs uppercase"
style={{ backgroundColor: 'var(--app-list-alt)', color: 'var(--app-fg)' }}
>
<div className="w-8 px-2 py-1">#</div>
<div className="w-6 px-1 py-1"></div>
<div className="px-2 py-1 flex-1">{t('table.path')}</div>
</div>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const rowIdx = virtualRow.index;
const { path, index, enabled } = filtered[rowIdx];
const v = validations[rowIdx];
const isSelected = selectedIndices.includes(index);
let textColor = 'var(--app-fg)';
if (v.state === 'invalid') textColor = '#dc3545';
else if (v.isDuplicate) textColor = '#fd7e14';
else if (v.state === 'unknown') textColor = 'var(--app-fg)';
let textDecoration = 'none';
let opacity = 1;
if (!enabled) {
textColor = '#6b7280';
textDecoration = 'line-through';
opacity = 0.6;
}
let textDecoration = 'none';
let opacity = 1;
if (!enabled) {
textColor = '#6b7280';
textDecoration = 'line-through';
opacity = 0.6;
}
return (
<tr
key={index}
onClick={(e) => handleClick(index, e)}
onDoubleClick={() => handleDoubleClick(index)}
className="cursor-pointer select-none"
style={{
backgroundColor: isSelected
? 'var(--app-select-row)'
: rowIdx % 2 === 0
? 'var(--app-list-bg)'
: 'var(--app-list-alt)',
}}
return (
<div
key={virtualRow.key}
onClick={(e) => handleClick(index, e)}
onDoubleClick={() => handleDoubleClick(index)}
className="cursor-pointer select-none flex items-center absolute top-0 left-0 w-full"
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
backgroundColor: isSelected
? 'var(--app-select-row)'
: rowIdx % 2 === 0
? 'var(--app-list-bg)'
: 'var(--app-list-alt)',
}}
>
<div className="w-8 px-2 py-0.5 text-xs opacity-50" style={{ color: 'var(--app-fg)' }}>
{index + 1}
</div>
<div className="w-6 px-1 py-0.5 flex items-center">
<input
type="checkbox"
checked={enabled}
onChange={() => {
const target = tabId === 'system' ? TargetType.SYSTEM : TargetType.USER;
useAppStore.getState().togglePath(index, target);
}}
className="cursor-pointer"
/>
</div>
<div
className="px-2 py-0.5 text-sm truncate flex-1"
style={{ color: textColor, textDecoration, opacity }}
title={expandedCache.get(path) || undefined}
>
<td className="w-8 px-2 py-0.5 text-xs opacity-50" style={{ color: 'var(--app-fg)' }}>
{index + 1}
</td>
<td className="w-6 px-1 py-0.5">
<input
type="checkbox"
checked={enabled}
onChange={() => {
const target = tabId === 'system' ? TargetType.SYSTEM : TargetType.USER;
useAppStore.getState().togglePath(index, target);
}}
className="cursor-pointer"
/>
</td>
<td
className="px-2 py-0.5 text-sm truncate max-w-2xl"
style={{ color: textColor, textDecoration, opacity }}
title={expandedCache.get(path) || undefined}
>
{path}
</td>
</tr>
);
})}
</tbody>
</table>
{path}
</div>
</div>
);
})}
</div>
</div>
);
}