mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 01:45:54 +08:00
feat: 重写为 Tauri + React + TypeScript (v4.0)
完全移除旧 C+IUP 代码,改用 Tauri 2.x + React 19 + TypeScript + Rust 技术栈重写。 功能与 v3.1 完全等价: - React 前端:Tailwind CSS 4、Zustand 状态管理、i18next 国际化 - Rust 后端:winreg 注册表读写、Win32 API FFI 调用 - 核心逻辑:StringList、UndoRedoManager、PathManager、Import/Export - 深色模式、中英文切换、键盘快捷键、合并预览 - 66 个 Vitest 单元测试 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { validatePath } from '@/hooks/use-path-validation';
|
||||
|
||||
interface PathTableProps {
|
||||
tabId: 'system' | 'user';
|
||||
}
|
||||
|
||||
export function PathTable({ tabId }: PathTableProps) {
|
||||
const sysPaths = useAppStore((s) => s.sysPaths);
|
||||
const userPaths = useAppStore((s) => s.userPaths);
|
||||
const searchQuery = useAppStore((s) => s.searchQuery);
|
||||
const selectedIndices = useAppStore((s) => s.selectedIndices);
|
||||
const setSelectedIndices = useAppStore((s) => s.setSelectedIndices);
|
||||
const activeTab = useAppStore((s) => s.activeTab);
|
||||
|
||||
const paths = tabId === 'system' ? sysPaths : userPaths;
|
||||
const isActive = activeTab === tabId;
|
||||
|
||||
// 过滤搜索
|
||||
const filtered = useMemo(() => {
|
||||
if (!searchQuery) return paths.all.map((p, i) => ({ path: p, index: i }));
|
||||
const q = searchQuery.toLowerCase();
|
||||
const result: { path: string; index: number }[] = [];
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
if (paths.get(i)!.toLowerCase().includes(q)) {
|
||||
result.push({ path: paths.get(i)!, index: i });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [paths, searchQuery]);
|
||||
|
||||
// 路径验证状态
|
||||
const validations = useMemo(() => {
|
||||
const seen = new Set<string>();
|
||||
return filtered.map(({ path }) => {
|
||||
const lower = path.toLowerCase();
|
||||
const isDuplicate = seen.has(lower);
|
||||
seen.add(lower);
|
||||
return {
|
||||
isValid: validatePath(path),
|
||||
isDuplicate,
|
||||
};
|
||||
});
|
||||
}, [filtered]);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(realIndex: number, e: React.MouseEvent) => {
|
||||
if (!isActive) return;
|
||||
if (e.ctrlKey) {
|
||||
const next = selectedIndices.includes(realIndex)
|
||||
? selectedIndices.filter((i) => i !== realIndex)
|
||||
: [...selectedIndices, realIndex];
|
||||
setSelectedIndices(next);
|
||||
} else {
|
||||
setSelectedIndices([realIndex]);
|
||||
}
|
||||
},
|
||||
[isActive, selectedIndices, setSelectedIndices],
|
||||
);
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(realIndex: number) => {
|
||||
if (!isActive) return;
|
||||
// 双击编辑 — 由 AppShell 处理(通过事件)
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('path-dblclick', {
|
||||
detail: { index: realIndex, path: paths.get(realIndex) },
|
||||
}),
|
||||
);
|
||||
},
|
||||
[isActive, paths],
|
||||
);
|
||||
|
||||
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="px-2 py-1">路径</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map(({ path, index }, rowIdx) => {
|
||||
const v = validations[rowIdx];
|
||||
const isSelected = selectedIndices.includes(index);
|
||||
let textColor = 'var(--app-fg)';
|
||||
if (!v.isValid) textColor = '#dc3545';
|
||||
else if (v.isDuplicate) textColor = '#fd7e14';
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
onClick={(e) => handleClick(index, e)}
|
||||
onDoubleClick={() => handleDoubleClick(index)}
|
||||
className="cursor-pointer select-none"
|
||||
style={{
|
||||
backgroundColor: isSelected
|
||||
? 'rgba(59, 130, 246, 0.3)'
|
||||
: rowIdx % 2 === 0
|
||||
? 'var(--app-list-bg)'
|
||||
: 'var(--app-list-alt)',
|
||||
}}
|
||||
>
|
||||
<td
|
||||
className="px-2 py-0.5 text-xs opacity-50"
|
||||
style={{ color: 'var(--app-fg)' }}
|
||||
>
|
||||
{index + 1}
|
||||
</td>
|
||||
<td className="px-2 py-0.5 text-sm" style={{ color: textColor }}>
|
||||
{path}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user