新增任务分享/导入功能并修复只读文件保存 EPERM
- flows: writeFlowFile 覆盖写加固(清只读属性->删除后 rename->覆盖写), 外部复制导入的只读任务文件此前导致 flows:save EPERM 崩溃 - flows: exportFlowToPath/importFlowFromPath, 分享文件为单个 FlowDefinition JSON(.rpaflow.json); 导入校验 schemaVersion/nodes/edges, id 冲突时生成 imported_ 前缀新 id 绝不覆盖既有任务 - IPC: flows:export 弹保存对话框 / flows:import 弹多选打开对话框, 失败文件逐个汇报不阻塞其余 - preload: flows.export/flows.import; shared: FlowsImportResult 类型 - TasksPage: 头部「导入任务」按钮 + 每行「分享」按钮; App 接线与 toast - 测试: 导出/导入往返、id 冲突、非法文件原因、只读覆盖共 4 例(7/7 绿)
This commit is contained in:
1 parent
22a7ee1595
commit
f95a24458e
8 files changed
+303
-10
No files matched your search
+45
-2
@@ -1,6 +1,6 @@
|
||||
import { app, BrowserWindow, dialog, ipcMain, screen, shell } from "electron";
|
||||
import path from "node:path";
|
||||
import type { AppInfo, AppSettings, FlowDefinition, FlowSaveOptions, FlowSaveResult } from "../shared/types";
|
||||
import type { AppInfo, AppSettings, FlowDefinition, FlowSaveOptions, FlowSaveResult, FlowSummary, FlowsImportResult } from "../shared/types";
|
||||
import {
|
||||
BrowserSession,
|
||||
externalBrowserSession,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "./browser/session";
|
||||
import "./capabilities";
|
||||
import { catalogPath, loadCatalog } from "./catalog";
|
||||
import { createDemoFlow, createExampleFlows, createFlow, deleteFlow, getFlow, listFlows, saveFlow } from "./flows";
|
||||
import { createDemoFlow, createExampleFlows, createFlow, deleteFlow, exportFlowToPath, getFlow, importFlowFromPath, listFlows, saveFlow, SHARE_EXTENSION } from "./flows";
|
||||
import { capabilityCount, capabilityIds, setHostBridge, setSubflowSource, type HostDialogWindow } from "@rpa/core";
|
||||
import { closeBrowserSession, setMainWindowGetter, startRun, stopRun } from "./run/manager";
|
||||
import { registerOverlayIpc } from "./overlay";
|
||||
@@ -214,6 +214,49 @@ function registerIpc(): void {
|
||||
deleteFlow(id);
|
||||
return true;
|
||||
});
|
||||
ipcMain.handle("flows:export", async (_event, id: string): Promise<string | null> => {
|
||||
if (!mainWindow) return null;
|
||||
const flow = getFlow(id);
|
||||
const safeName = (flow.name || "任务").replace(/[\\/:*?"<>|]/g, "_").slice(0, 60);
|
||||
const result = await dialog.showSaveDialog(mainWindow, {
|
||||
title: "分享任务",
|
||||
defaultPath: `${safeName}.${SHARE_EXTENSION}.json`,
|
||||
filters: [
|
||||
{ name: "RPA 任务分享文件", extensions: [SHARE_EXTENSION, "json"] },
|
||||
{ name: "所有文件", extensions: ["*"] }
|
||||
]
|
||||
});
|
||||
if (result.canceled || !result.filePath) return null;
|
||||
return exportFlowToPath(id, result.filePath);
|
||||
});
|
||||
ipcMain.handle("flows:import", async (): Promise<FlowsImportResult> => {
|
||||
if (!mainWindow) return { imported: [], failed: [] };
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
title: "导入任务",
|
||||
properties: ["openFile", "multiSelections"],
|
||||
filters: [
|
||||
{ name: "RPA 任务分享文件", extensions: [SHARE_EXTENSION, "json"] },
|
||||
{ name: "所有文件", extensions: ["*"] }
|
||||
]
|
||||
});
|
||||
if (result.canceled || !result.filePaths.length) return { imported: [], failed: [] };
|
||||
const imported: FlowSummary[] = [];
|
||||
const failed: Array<{ file: string; reason: string }> = [];
|
||||
for (const filePath of result.filePaths) {
|
||||
try {
|
||||
const flow = importFlowFromPath(filePath);
|
||||
imported.push(listFlows().find((item) => item.id === flow.id) || {
|
||||
id: flow.id,
|
||||
name: flow.name,
|
||||
updatedAt: flow.meta?.updatedAt || new Date().toISOString(),
|
||||
nodeCount: flow.nodes.length
|
||||
});
|
||||
} catch (error) {
|
||||
failed.push({ file: path.basename(filePath), reason: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
return { imported, failed };
|
||||
});
|
||||
|
||||
ipcMain.handle("dialog:open", async (_event, options: { title?: string; filters?: Electron.FileFilter[]; directories?: boolean }) => {
|
||||
if (!mainWindow) return null;
|
||||
|
||||
Reference in new issue
Block a user