mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 01:45:54 +08:00
refactor: AppShell 拆分 + savePaths 并行化
- 抽取 useAppActions hook(~160行),AppShell 从 306 行精简至 105 行 - AppShell 现在只负责布局和渲染,操作逻辑全部可单独测试 - savePaths 改为 Promise.allSettled 并行保存 + 并行备份 - useKeyboard 通过 hook 内部集成,不再暴露给 AppShell Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useAppStore, type TabId } from '@/store/app-store';
|
||||
import { useThemeStore } from '@/store/theme-store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '@/i18n';
|
||||
import { TargetType } from '@/core/undo-redo';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { importFromContent, exportToJson, flattenImportResult } from '@/core/import-export';
|
||||
import { StatusBar } from './StatusBar';
|
||||
import { TitleBar } from './TitleBar';
|
||||
import { ToolBar } from '@/components/toolbar/ToolBar';
|
||||
@@ -14,177 +12,28 @@ import { MergePreview } from '@/components/path-list/MergePreview';
|
||||
import { PathEditDialog } from '@/components/dialogs/PathEditDialog';
|
||||
import { HelpDialog } from '@/components/dialogs/HelpDialog';
|
||||
import { ImportDialog } from '@/components/dialogs/ImportDialog';
|
||||
import { useKeyboard } from '@/hooks/use-keyboard';
|
||||
import { useAppActions, type DialogState } from '@/hooks/use-app-actions';
|
||||
|
||||
export function AppShell() {
|
||||
const { t } = useTranslation();
|
||||
const activeTab = useAppStore((s) => s.activeTab);
|
||||
const setActiveTab = useAppStore((s) => s.setActiveTab);
|
||||
const selectedIndices = useAppStore((s) => s.selectedIndices);
|
||||
const setSelectedIndices = useAppStore((s) => s.setSelectedIndices);
|
||||
|
||||
// 对话弹窗状态
|
||||
const [editDialog, setEditDialog] = useState<{ open: boolean; index: number; value: string; target: TargetType }>({
|
||||
const [editDialog, setEditDialog] = useState<DialogState['editDialog']>({
|
||||
open: false, index: -1, value: '', target: TargetType.SYSTEM,
|
||||
});
|
||||
const [newDialog, setNewDialog] = useState(false);
|
||||
const [helpOpen, setHelpOpen] = useState(false);
|
||||
const [importDialog, setImportDialog] = useState<{ open: boolean; system: string[]; user: string[] }>({
|
||||
const [importDialog, setImportDialog] = useState<DialogState['importDialog']>({
|
||||
open: false, system: [], user: [],
|
||||
});
|
||||
|
||||
// ── 操作处理 ──
|
||||
|
||||
const getCurrentTarget = useCallback((): TargetType => {
|
||||
return activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM;
|
||||
}, [activeTab]);
|
||||
|
||||
const handleNew = useCallback(() => {
|
||||
setNewDialog(true);
|
||||
}, []);
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
if (selectedIndices.length !== 1) return;
|
||||
const idx = selectedIndices[0];
|
||||
const target = getCurrentTarget();
|
||||
const list = target === TargetType.SYSTEM
|
||||
? useAppStore.getState().sysPaths
|
||||
: useAppStore.getState().userPaths;
|
||||
const value = list[idx];
|
||||
if (value) {
|
||||
setEditDialog({ open: true, index: idx, value, target });
|
||||
}
|
||||
}, [selectedIndices, getCurrentTarget]);
|
||||
|
||||
const handleBrowse = useCallback(async () => {
|
||||
const selected = await open({ directory: true, multiple: false });
|
||||
if (selected && typeof selected === 'string') {
|
||||
useAppStore.getState().addPath(selected, getCurrentTarget());
|
||||
}
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
if (selectedIndices.length === 0) return;
|
||||
useAppStore.getState().deletePaths(selectedIndices, getCurrentTarget());
|
||||
}, [selectedIndices, getCurrentTarget]);
|
||||
|
||||
const handleMoveUp = useCallback(() => {
|
||||
if (selectedIndices.length !== 1) return;
|
||||
useAppStore.getState().moveUp(selectedIndices[0], getCurrentTarget());
|
||||
}, [selectedIndices, getCurrentTarget]);
|
||||
|
||||
const handleMoveDown = useCallback(() => {
|
||||
if (selectedIndices.length !== 1) return;
|
||||
useAppStore.getState().moveDown(selectedIndices[0], getCurrentTarget());
|
||||
}, [selectedIndices, getCurrentTarget]);
|
||||
|
||||
const handleClean = useCallback(() => {
|
||||
const removed = useAppStore.getState().cleanPaths(
|
||||
getCurrentTarget(),
|
||||
(p) => p.includes('%') || p.includes('\\') || p.includes('/') || /^[a-zA-Z]:[/\\]/.test(p),
|
||||
);
|
||||
if (removed.length > 0) {
|
||||
useAppStore.getState().setStatusMessage(
|
||||
t('status.deleted', { count: removed.length }),
|
||||
);
|
||||
}
|
||||
}, [getCurrentTarget, t]);
|
||||
|
||||
const handleImport = useCallback(() => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json,.csv,.txt';
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!file) { input.remove(); return; }
|
||||
const content = await file.text();
|
||||
const result = importFromContent(content, file.name);
|
||||
input.remove();
|
||||
|
||||
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);
|
||||
} else if (result.user.length > 0) {
|
||||
useAppStore.getState().importPaths(TargetType.USER, result.user);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}, []);
|
||||
|
||||
const handleExport = useCallback(() => {
|
||||
const state = useAppStore.getState();
|
||||
const data = { system: state.sysPaths, user: state.userPaths };
|
||||
|
||||
const content = exportToJson(data);
|
||||
const mime = 'application/json';
|
||||
const ext = '.json';
|
||||
|
||||
const blob = new Blob([content], { type: mime });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `patheditor_export${ext}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}, []);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
useAppStore.getState().savePaths();
|
||||
}, []);
|
||||
|
||||
// ── 键盘快捷键 ──
|
||||
|
||||
useKeyboard({
|
||||
onNew: handleNew,
|
||||
onSave: handleSave,
|
||||
onDelete: handleDelete,
|
||||
onUndo: () => useAppStore.getState().undo(),
|
||||
onRedo: () => useAppStore.getState().redo(),
|
||||
onHelp: () => setHelpOpen(true),
|
||||
const actions = useAppActions(activeTab, {
|
||||
editDialog, newDialog, helpOpen, importDialog,
|
||||
setEditDialog, setNewDialog, setHelpOpen, setImportDialog,
|
||||
});
|
||||
|
||||
// ── 双击编辑监听 ──
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
if (detail && typeof detail.index === 'number') {
|
||||
const target = getCurrentTarget();
|
||||
setEditDialog({ open: true, index: detail.index, value: detail.path, target });
|
||||
}
|
||||
};
|
||||
window.addEventListener('path-dblclick', handler);
|
||||
return () => window.removeEventListener('path-dblclick', handler);
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleNewConfirm = useCallback((value: string) => {
|
||||
setNewDialog(false);
|
||||
if (value.trim()) {
|
||||
useAppStore.getState().addPath(value.trim(), getCurrentTarget());
|
||||
}
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleEditConfirm = useCallback((value: string) => {
|
||||
setEditDialog({ open: false, index: -1, value: '', target: TargetType.SYSTEM });
|
||||
if (value.trim()) {
|
||||
useAppStore.getState().editPath(editDialog.index, value.trim(), editDialog.target);
|
||||
}
|
||||
}, [editDialog]);
|
||||
|
||||
const handleImportSelect = useCallback((target: 'system' | 'user' | 'both') => {
|
||||
const { system, user } = 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);
|
||||
}
|
||||
setImportDialog({ open: false, system: [], user: [] });
|
||||
}, [importDialog]);
|
||||
|
||||
// Tab 切换
|
||||
const tabConfig: { id: TabId; label: string }[] = [
|
||||
{ id: 'system', label: t('tab.system') },
|
||||
{ id: 'user', label: t('tab.user') },
|
||||
@@ -192,27 +41,15 @@ export function AppShell() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col h-screen"
|
||||
style={{ backgroundColor: 'var(--app-bg)', color: 'var(--app-fg)' }}
|
||||
>
|
||||
<div className="flex flex-col h-screen" style={{ backgroundColor: 'var(--app-bg)', color: 'var(--app-fg)' }}>
|
||||
<TitleBar />
|
||||
|
||||
{/* Tab 栏 */}
|
||||
<div
|
||||
className="flex border-b px-4"
|
||||
style={{ borderColor: 'var(--app-border)' }}
|
||||
>
|
||||
<div className="flex border-b px-4" style={{ borderColor: 'var(--app-border)' }}>
|
||||
{tabConfig.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => {
|
||||
setActiveTab(tab.id);
|
||||
setSelectedIndices([]);
|
||||
}}
|
||||
className={`px-4 py-1.5 text-sm font-medium transition-colors ${
|
||||
activeTab === tab.id ? 'tab-active' : 'opacity-60'
|
||||
}`}
|
||||
onClick={() => { setActiveTab(tab.id); setSelectedIndices([]); }}
|
||||
className={`px-4 py-1.5 text-sm font-medium transition-colors ${activeTab === tab.id ? 'tab-active' : 'opacity-60'}`}
|
||||
style={{ color: activeTab === tab.id ? '#3b82f6' : 'var(--app-fg)' }}
|
||||
>
|
||||
{tab.label}
|
||||
@@ -220,19 +57,18 @@ export function AppShell() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 工具栏 */}
|
||||
<div className="px-4 py-2">
|
||||
<ToolBar
|
||||
onNew={handleNew}
|
||||
onEdit={handleEdit}
|
||||
onBrowse={handleBrowse}
|
||||
onDelete={handleDelete}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
onClean={handleClean}
|
||||
onImport={handleImport}
|
||||
onExport={handleExport}
|
||||
onSave={handleSave}
|
||||
onNew={actions.handleNew}
|
||||
onEdit={actions.handleEdit}
|
||||
onBrowse={actions.handleBrowse}
|
||||
onDelete={actions.handleDelete}
|
||||
onMoveUp={actions.handleMoveUp}
|
||||
onMoveDown={actions.handleMoveDown}
|
||||
onClean={actions.handleClean}
|
||||
onImport={actions.handleImport}
|
||||
onExport={actions.handleExport}
|
||||
onSave={actions.handleSave}
|
||||
onCancel={() => {
|
||||
const state = useAppStore.getState();
|
||||
if (state.isModified && !window.confirm('有未保存的修改,确定退出吗?')) return;
|
||||
@@ -247,60 +83,27 @@ export function AppShell() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 路径列表(支持拖拽文件夹) */}
|
||||
<div
|
||||
className="flex-1 overflow-hidden"
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'link';
|
||||
}}
|
||||
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'link'; }}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
if (activeTab === 'merged') return;
|
||||
const files = e.dataTransfer.files;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const path = (files[i] as any).path;
|
||||
if (path) {
|
||||
useAppStore.getState().addPath(path, getCurrentTarget());
|
||||
}
|
||||
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
const path = (e.dataTransfer.files[i] as any).path;
|
||||
if (path) useAppStore.getState().addPath(path, activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{activeTab === 'merged' ? (
|
||||
<MergePreview />
|
||||
) : (
|
||||
<PathTable tabId={activeTab as 'system' | 'user'} />
|
||||
)}
|
||||
{activeTab === 'merged' ? <MergePreview /> : <PathTable tabId={activeTab as 'system' | 'user'} />}
|
||||
</div>
|
||||
|
||||
<StatusBar />
|
||||
|
||||
{/* 弹窗 */}
|
||||
<PathEditDialog
|
||||
open={newDialog}
|
||||
title={t('dialog.newPath')}
|
||||
initialValue=""
|
||||
onConfirm={handleNewConfirm}
|
||||
onCancel={() => setNewDialog(false)}
|
||||
/>
|
||||
|
||||
<PathEditDialog
|
||||
open={editDialog.open}
|
||||
title={t('dialog.editPath')}
|
||||
initialValue={editDialog.value}
|
||||
onConfirm={handleEditConfirm}
|
||||
onCancel={() => setEditDialog({ open: false, index: -1, value: '', target: TargetType.SYSTEM })}
|
||||
/>
|
||||
|
||||
<PathEditDialog open={newDialog} title={t('dialog.newPath')} initialValue="" onConfirm={actions.handleNewConfirm} onCancel={() => setNewDialog(false)} />
|
||||
<PathEditDialog open={editDialog.open} title={t('dialog.editPath')} initialValue={editDialog.value} onConfirm={actions.handleEditConfirm} onCancel={() => setEditDialog({ open: false, index: -1, value: '', target: TargetType.SYSTEM })} />
|
||||
<HelpDialog open={helpOpen} onClose={() => setHelpOpen(false)} />
|
||||
|
||||
<ImportDialog
|
||||
open={importDialog.open}
|
||||
systemCount={importDialog.system.length}
|
||||
userCount={importDialog.user.length}
|
||||
onSelect={handleImportSelect}
|
||||
onCancel={() => setImportDialog({ open: false, system: [], user: [] })}
|
||||
/>
|
||||
<ImportDialog open={importDialog.open} systemCount={importDialog.system.length} userCount={importDialog.user.length} onSelect={actions.handleImportSelect} onCancel={() => setImportDialog({ open: false, system: [], user: [] })} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { TargetType } from '@/core/undo-redo';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { importFromContent, exportToJson, flattenImportResult } from '@/core/import-export';
|
||||
import { useKeyboard } from './use-keyboard';
|
||||
import i18n from '@/i18n';
|
||||
import type { TabId } from '@/store/app-store';
|
||||
|
||||
export interface DialogState {
|
||||
editDialog: { open: boolean; index: number; value: string; target: TargetType };
|
||||
newDialog: boolean;
|
||||
helpOpen: boolean;
|
||||
importDialog: { open: boolean; system: string[]; user: string[] };
|
||||
setEditDialog: (v: DialogState['editDialog']) => void;
|
||||
setNewDialog: (v: boolean) => void;
|
||||
setHelpOpen: (v: boolean) => void;
|
||||
setImportDialog: (v: DialogState['importDialog']) => void;
|
||||
}
|
||||
|
||||
export function useAppActions(activeTab: TabId, dialogs: DialogState) {
|
||||
const { setEditDialog, setNewDialog, setHelpOpen, setImportDialog } = dialogs;
|
||||
|
||||
const getCurrentTarget = useCallback((): TargetType => {
|
||||
return activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM;
|
||||
}, [activeTab]);
|
||||
|
||||
// ── CRUD ──
|
||||
|
||||
const handleNew = useCallback(() => setNewDialog(true), [setNewDialog]);
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
const idx = useAppStore.getState().selectedIndices[0];
|
||||
if (idx === undefined) return;
|
||||
const target = activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM;
|
||||
const list = target === TargetType.SYSTEM
|
||||
? useAppStore.getState().sysPaths
|
||||
: useAppStore.getState().userPaths;
|
||||
const value = list[idx];
|
||||
if (value) setEditDialog({ open: true, index: idx, value, target });
|
||||
}, [activeTab, setEditDialog]);
|
||||
|
||||
const handleBrowse = useCallback(async () => {
|
||||
const selected = await open({ directory: true, multiple: false });
|
||||
if (selected && typeof selected === 'string') {
|
||||
useAppStore.getState().addPath(selected, getCurrentTarget());
|
||||
}
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
const idx = useAppStore.getState().selectedIndices;
|
||||
if (idx.length === 0) return;
|
||||
useAppStore.getState().deletePaths(idx, getCurrentTarget());
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleMoveUp = useCallback(() => {
|
||||
const idx = useAppStore.getState().selectedIndices[0];
|
||||
if (idx === undefined) return;
|
||||
useAppStore.getState().moveUp(idx, getCurrentTarget());
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleMoveDown = useCallback(() => {
|
||||
const idx = useAppStore.getState().selectedIndices[0];
|
||||
if (idx === undefined) return;
|
||||
useAppStore.getState().moveDown(idx, getCurrentTarget());
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
const handleClean = useCallback(() => {
|
||||
const removed = useAppStore.getState().cleanPaths(
|
||||
getCurrentTarget(),
|
||||
(p) => p.includes('%') || p.includes('\\') || p.includes('/') || /^[a-zA-Z]:[/\\]/.test(p),
|
||||
);
|
||||
if (removed.length > 0) {
|
||||
useAppStore.getState().setStatusMessage(
|
||||
i18n.t('status.deleted', { count: removed.length }),
|
||||
);
|
||||
}
|
||||
}, [getCurrentTarget]);
|
||||
|
||||
// ── 导入导出 ──
|
||||
|
||||
const handleImport = useCallback(() => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json,.csv,.txt';
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!file) { input.remove(); return; }
|
||||
const content = await file.text();
|
||||
const result = importFromContent(content, file.name);
|
||||
input.remove();
|
||||
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);
|
||||
} else if (result.user.length > 0) {
|
||||
useAppStore.getState().importPaths(TargetType.USER, result.user);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}, [setImportDialog]);
|
||||
|
||||
const handleExport = useCallback(() => {
|
||||
const state = useAppStore.getState();
|
||||
const content = exportToJson({ system: state.sysPaths, user: state.userPaths });
|
||||
const blob = new Blob([content], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'patheditor_export.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}, []);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
useAppStore.getState().savePaths();
|
||||
}, []);
|
||||
|
||||
// ── 键盘 ──
|
||||
|
||||
useKeyboard({
|
||||
onNew: handleNew,
|
||||
onSave: handleSave,
|
||||
onDelete: handleDelete,
|
||||
onUndo: () => useAppStore.getState().undo(),
|
||||
onRedo: () => useAppStore.getState().redo(),
|
||||
onHelp: () => setHelpOpen(true),
|
||||
});
|
||||
|
||||
// ── 双击编辑 ──
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
if (detail && typeof detail.index === 'number') {
|
||||
const target = getCurrentTarget();
|
||||
setEditDialog({ open: true, index: detail.index, value: detail.path, target });
|
||||
}
|
||||
};
|
||||
window.addEventListener('path-dblclick', handler);
|
||||
return () => window.removeEventListener('path-dblclick', handler);
|
||||
}, [getCurrentTarget, setEditDialog]);
|
||||
|
||||
// ── 弹窗确认 ──
|
||||
|
||||
const handleNewConfirm = useCallback((value: string) => {
|
||||
setNewDialog(false);
|
||||
if (value.trim()) useAppStore.getState().addPath(value.trim(), getCurrentTarget());
|
||||
}, [getCurrentTarget, setNewDialog]);
|
||||
|
||||
const handleEditConfirm = useCallback((value: string) => {
|
||||
const d = dialogs.editDialog;
|
||||
setEditDialog({ open: false, index: -1, value: '', target: TargetType.SYSTEM });
|
||||
if (value.trim()) useAppStore.getState().editPath(d.index, value.trim(), d.target);
|
||||
}, [dialogs.editDialog, setEditDialog]);
|
||||
|
||||
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);
|
||||
setImportDialog({ open: false, system: [], user: [] });
|
||||
}, [dialogs.importDialog, setImportDialog]);
|
||||
|
||||
return {
|
||||
handleNew, handleEdit, handleBrowse, handleDelete,
|
||||
handleMoveUp, handleMoveDown, handleClean,
|
||||
handleImport, handleExport, handleSave,
|
||||
handleNewConfirm, handleEditConfirm, handleImportSelect,
|
||||
};
|
||||
}
|
||||
+11
-6
@@ -230,15 +230,20 @@ export const useAppStore = create<AppState>((set, get) => ({
|
||||
|
||||
set({ statusMessage: i18n.t('status.saving') });
|
||||
|
||||
// 保存前备份
|
||||
try { await invoke('backup_registry', { customDir: null, sysPaths, userPaths }); } catch { /* 备份失败不阻止保存 */ }
|
||||
// 备份(不阻塞保存)
|
||||
invoke('backup_registry', { customDir: null, sysPaths, userPaths }).catch(() => {});
|
||||
|
||||
let sysOk = true, userOk = true;
|
||||
try { await invoke('save_system_paths', { paths: sysPaths }); } catch { sysOk = false; }
|
||||
try { await invoke('save_user_paths', { paths: userPaths }); } catch { userOk = false; }
|
||||
// 并行保存
|
||||
const [sysResult, userResult] = await Promise.allSettled([
|
||||
invoke('save_system_paths', { paths: sysPaths }),
|
||||
invoke('save_user_paths', { paths: userPaths }),
|
||||
]);
|
||||
|
||||
const sysOk = sysResult.status === 'fulfilled';
|
||||
const userOk = userResult.status === 'fulfilled';
|
||||
|
||||
if (sysOk && userOk) {
|
||||
try { await invoke('broadcast_env_change'); } catch { /* 广播失败不阻止 */ }
|
||||
invoke('broadcast_env_change').catch(() => {});
|
||||
set({ isModified: false, statusMessage: i18n.t('status.saved') });
|
||||
} else if (sysOk) {
|
||||
set({ statusMessage: '用户 PATH 保存失败,系统 PATH 已保存' });
|
||||
|
||||
Reference in New Issue
Block a user