fix: 全面审查修复 14 个 bug,新增 Rust 单元测试

CRITICAL:
- PathTable/MergePreview 操作后不重新渲染(加 dataVersion 版本号机制)
- moveUp/moveDown 后 selectedIndices 过时(更新到新位置)

HIGH:
- ImportDialog 显示 "true" 而非路径数量(改为 number 类型)
- F1 快捷键无效果(添加 onHelp 回调)
- useKeyboard 每次渲染重复注册事件(改用 ref 模式)
- batch delete 撤销顺序错误(拆分为独立记录)
- importPaths 存储数组引用而非副本
- StringList.all 暴露内部数组(改为返回副本)
- expand_env_vars 静默吞 API 错误(加 log::warn)
- join_path 写入前未修剪路径(加 trim 避免注册表污染)

MEDIUM:
- handleClean 总传 () => true 不验证无效路径
- HelpDialog/ImportDialog 缺 Escape 关闭
- initDarkMode 不同步 Zustand store
- 多处硬编码中文改为 i18n.t()
- Rust unsafe 块补全 SAFETY 注释

新增 Rust 测试: system.rs 4 个单元测试

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 23:14:26 +08:00
parent b1acb3690c
commit 2ceec54790
11 changed files with 173 additions and 93 deletions
+19 -12
View File
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { useAppStore } from '@/store/app-store';
interface KeyboardActions {
@@ -7,18 +7,21 @@ interface KeyboardActions {
onDelete: () => void;
onUndo: () => void;
onRedo: () => void;
onHelp: () => void;
}
/**
* 全局键盘快捷键
* Ctrl+N 新建, Ctrl+S 保存, Ctrl+Z 撤销, Ctrl+Y 重做, Delete 删除
* Ctrl+N 新建, Ctrl+S 保存, Ctrl+Z 撤销, Ctrl+Y 重做, Delete 删除, F1 帮助
* 使用 ref 避免因 actions 对象每次渲染都是新引用而重复注册事件
*/
export function useKeyboard(actions: KeyboardActions) {
const isAdmin = useAppStore((s) => s.isAdmin);
const actionsRef = useRef(actions);
actionsRef.current = actions;
useEffect(() => {
const handler = (e: KeyboardEvent) => {
// 如果焦点在输入框中,只响应 Escape
const tag = (e.target as HTMLElement)?.tagName;
const isInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
@@ -29,32 +32,36 @@ export function useKeyboard(actions: KeyboardActions) {
return;
}
if (!isAdmin) return;
const a = actionsRef.current;
const ctrl = e.ctrlKey || e.metaKey;
if (ctrl && e.key === 'z') {
if (!isAdmin) return;
e.preventDefault();
actions.onUndo();
a.onUndo();
} else if (ctrl && e.key === 'y') {
if (!isAdmin) return;
e.preventDefault();
actions.onRedo();
a.onRedo();
} else if (ctrl && e.key === 'n') {
if (!isAdmin) return;
e.preventDefault();
actions.onNew();
a.onNew();
} else if (ctrl && e.key === 's') {
if (!isAdmin) return;
e.preventDefault();
actions.onSave();
a.onSave();
} else if (e.key === 'Delete' || e.key === 'Backspace') {
if (!isAdmin) return;
e.preventDefault();
actions.onDelete();
a.onDelete();
} else if (e.key === 'F1') {
e.preventDefault();
// 帮助由 AppShell 处理
a.onHelp();
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [isAdmin, actions]);
}, [isAdmin]); // 只依赖 isAdminactions 通过 ref 读取
}