diff --git a/src-tauri/src/commands/registry.rs b/src-tauri/src/commands/registry.rs index 29bffa8..f062007 100644 --- a/src-tauri/src/commands/registry.rs +++ b/src-tauri/src/commands/registry.rs @@ -63,6 +63,8 @@ pub fn save_user_paths(paths: Vec) -> Result<(), String> { save_paths(HKEY_CURRENT_USER, USER_REG_PATH, "用户", &paths) } +/// 将分号分隔的 PATH 字符串拆分为数组。 +/// 注意:TS 端 src/core/validation.ts 有相同逻辑的 split_path,修改时需同步两端。 pub(crate) fn split_path(raw: &str) -> Vec { raw.split(';') .map(|s| s.trim().to_string()) diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs deleted file mode 100644 index 97ca0e5..0000000 --- a/src-tauri/src/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -use serde::Serialize; - -/// 传给前端的统一错误类型(保留供未来迁移使用,届时所有命令改为返回 Result) -#[allow(dead_code)] -#[derive(Debug, Serialize)] -pub struct AppError { - pub message: String, -} - -impl std::fmt::Display for AppError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl From<&str> for AppError { - fn from(s: &str) -> Self { - AppError { - message: s.to_string(), - } - } -} - -impl From for AppError { - fn from(s: String) -> Self { - AppError { message: s } - } -} - -impl From for AppError { - fn from(e: std::io::Error) -> Self { - AppError { - message: format!("IO 错误: {}", e), - } - } -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fe116f4..a5294ee 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,5 +1,4 @@ mod commands; -mod error; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { diff --git a/src/config/default.json b/src/config/default.json index e5775a1..4f078d9 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -12,8 +12,8 @@ "dir": "" }, "path": { - "maxSystemLength": 2048, - "maxUserLength": 2048, - "maxCombinedLength": 8191 + "maxSystemLength": 32767, + "maxUserLength": 32767, + "maxCombinedLength": 32767 } } diff --git a/src/core/import-export.ts b/src/core/import-export.ts index 3cbf1e0..ba62c13 100644 --- a/src/core/import-export.ts +++ b/src/core/import-export.ts @@ -2,7 +2,7 @@ * 导入导出模块 — 对应 C 版 import_export.c * 支持 JSON、CSV、TXT 三种格式 */ -export type ExportFormat = 'json' | 'csv'; +export type ExportFormat = 'json' | 'csv' | 'txt'; export interface ExportData { system: string[]; @@ -11,7 +11,9 @@ export interface ExportData { /** 根据文件扩展名检测格式 */ export function detectExportFormat(filepath: string): ExportFormat { - if (filepath.toLowerCase().endsWith('.csv')) return 'csv'; + const lower = filepath.toLowerCase(); + if (lower.endsWith('.csv')) return 'csv'; + if (lower.endsWith('.txt')) return 'txt'; return 'json'; } @@ -65,10 +67,10 @@ export function importFromCsv(content: string): ImportResult { let hasHeader = false; - for (const rawLine of lines) { - // 跳过 BOM - let line = rawLine; - if (line.startsWith('')) { + for (let i = 0; i < lines.length; i++) { + // 跳过 BOM(仅首行) + let line = lines[i]; + if (i === 0 && line.startsWith('')) { line = line.slice(1); } @@ -174,9 +176,10 @@ export function importFromTxt(content: string): string[] { const paths: string[] = []; const lines = content.split(/\r?\n/); - for (let line of lines) { - // 跳过 BOM - if (line.startsWith('')) line = line.slice(1); + for (let i = 0; i < lines.length; i++) { + // 跳过 BOM(仅首行) + let line = lines[i]; + if (i === 0 && line.startsWith('')) line = line.slice(1); const trimmed = line.trim(); if (trimmed.length === 0 || trimmed.startsWith('#')) continue; diff --git a/src/core/validation.ts b/src/core/validation.ts index b10743b..80a030d 100644 --- a/src/core/validation.ts +++ b/src/core/validation.ts @@ -26,7 +26,8 @@ export function join_path(paths: string[]): string { return paths.join(';'); } -/** 分割 PATH 字符串 */ +/** 分割 PATH 字符串。 + * 注意:Rust 端 src-tauri/src/commands/registry.rs 有相同逻辑的 split_path,修改时需同步两端。 */ export function split_path(raw: string): string[] { return raw .split(';') diff --git a/src/hooks/use-app-actions.ts b/src/hooks/use-app-actions.ts index b42c820..1541912 100644 --- a/src/hooks/use-app-actions.ts +++ b/src/hooks/use-app-actions.ts @@ -92,9 +92,9 @@ export function useAppActions(activeTab: TabId, dialogs: DialogState) { if (result.system.length > 0 && result.user.length > 0) { setImportDialog({ open: true, system: result.system, user: result.user }); } else if (result.system.length > 0) { - useAppStore.getState().importPaths(TargetType.SYSTEM, result.system); + useAppStore.getState().replacePaths(TargetType.SYSTEM, result.system); } else if (result.user.length > 0) { - useAppStore.getState().importPaths(TargetType.USER, result.user); + useAppStore.getState().replacePaths(TargetType.USER, result.user); } }, [setImportDialog]); @@ -159,8 +159,8 @@ export function useAppActions(activeTab: TabId, dialogs: DialogState) { const handleImportSelect = useCallback((target: 'system' | 'user' | 'both') => { const { system, user } = dialogs.importDialog; const flat = flattenImportResult({ system, user }, target); - if (flat.system.length > 0) useAppStore.getState().importPaths(TargetType.SYSTEM, flat.system); - if (flat.user.length > 0) useAppStore.getState().importPaths(TargetType.USER, flat.user); + if (flat.system.length > 0) useAppStore.getState().replacePaths(TargetType.SYSTEM, flat.system); + if (flat.user.length > 0) useAppStore.getState().replacePaths(TargetType.USER, flat.user); setImportDialog({ open: false, system: [], user: [] }); }, [dialogs.importDialog, setImportDialog]); diff --git a/src/store/app-store.ts b/src/store/app-store.ts index 3ce9de2..509b4f6 100644 --- a/src/store/app-store.ts +++ b/src/store/app-store.ts @@ -34,7 +34,7 @@ interface AppState { moveUp: (index: number, target: TargetType) => void; moveDown: (index: number, target: TargetType) => void; cleanPaths: (target: TargetType, validateFn: (p: string) => boolean) => string[]; - importPaths: (target: TargetType, importPaths: string[]) => void; + replacePaths: (target: TargetType, newPaths: string[]) => void; clearPaths: (target: TargetType) => void; undo: () => void; @@ -169,18 +169,18 @@ export const useAppStore = create((set, get) => ({ return removed; }, - importPaths: (target, importPaths) => { - if (importPaths.length === 0) return; + replacePaths: (target, newPaths) => { + if (newPaths.length === 0) return; const state = get(); const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths; state.undoRedo.push({ - type: OperationType.IMPORT, target, index: 0, count: importPaths.length, - oldPaths: [...list], newPaths: [...importPaths], + type: OperationType.IMPORT, target, index: 0, count: newPaths.length, + oldPaths: [...list], newPaths: [...newPaths], }); - if (target === TargetType.SYSTEM) set({ sysPaths: [...importPaths], selectedIndices: [] }); - else set({ userPaths: [...importPaths], selectedIndices: [] }); + if (target === TargetType.SYSTEM) set({ sysPaths: [...newPaths], selectedIndices: [] }); + else set({ userPaths: [...newPaths], selectedIndices: [] }); get()._markDirty(); }, diff --git a/tests/unit/app-store.test.ts b/tests/unit/app-store.test.ts index de73fea..c2243f2 100644 --- a/tests/unit/app-store.test.ts +++ b/tests/unit/app-store.test.ts @@ -140,11 +140,11 @@ describe('app-store CRUD', () => { expect(useAppStore.getState().sysPaths).toEqual(['C:\\valid']); }); - it('importPaths 整体替换列表', () => { + it('replacePaths 整体替换列表', () => { const store = useAppStore.getState(); store.addPath('old1', TargetType.USER); store.addPath('old2', TargetType.USER); - store.importPaths(TargetType.USER, ['new1', 'new2', 'new3']); + store.replacePaths(TargetType.USER, ['new1', 'new2', 'new3']); expect(useAppStore.getState().userPaths).toEqual(['new1', 'new2', 'new3']); }); diff --git a/tests/unit/import-export.test.ts b/tests/unit/import-export.test.ts index b49a2d6..5c957cf 100644 --- a/tests/unit/import-export.test.ts +++ b/tests/unit/import-export.test.ts @@ -116,10 +116,11 @@ describe('detectExportFormat', () => { it('.csv 检测为 CSV', () => { expect(detectExportFormat('data.CSV')).toBe('csv'); }); - + it('.txt 检测为 TXT', () => { + expect(detectExportFormat('data.txt')).toBe('txt'); + }); it('其他扩展名检测为 JSON', () => { expect(detectExportFormat('data.json')).toBe('json'); - expect(detectExportFormat('data.txt')).toBe('json'); }); });