feat: 画布式 RPA 桌面端 Day1(1016 目录/画布/容器/属性/持久化)

This commit is contained in:
yu committed 2026-10-26 14:38:17 +08:00
commit 32ca0a1af8
34 files changed
+15141

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
import { app } from "electron";
import fs from "node:fs";
import path from "node:path";
import type { FlowDefinition, FlowSummary } from "../shared/types";
const ID_PATTERN = /^[A-Za-z0-9_-]{3,80}$/;
function dir(): string {
const value = path.join(app.getPath("userData"), "flows");
fs.mkdirSync(value, { recursive: true });
return value;
}
function fileOf(id: string): string {
if (!ID_PATTERN.test(id)) throw new Error(`invalid flow id: ${id}`);
return path.join(dir(), `${id}.json`);
}
export function createFlow(name = "未命名流程"): FlowDefinition {
const now = new Date().toISOString();
const id = `flow_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
return {
schemaVersion: 1,
id,
name,
description: "",
variables: [],
nodes: [
{
id: "node_start",
componentId: "Trigger.Manual",
position: { x: 160, y: 120 },
values: { entryName: "立即执行" },
options: { timeoutMs: 30000 }
}
],
edges: [],
meta: { createdAt: now, updatedAt: now, appVersion: app.getVersion() }
};
}
export function listFlows(): FlowSummary[] {
const base = dir();
const rows: FlowSummary[] = [];
for (const entry of fs.readdirSync(base)) {
if (!entry.endsWith(".json")) continue;
try {
const flow = JSON.parse(fs.readFileSync(path.join(base, entry), "utf8")) as FlowDefinition;
if (!flow?.id || !Array.isArray(flow.nodes)) continue;
rows.push({
id: flow.id,
name: flow.name || "未命名流程",
updatedAt: flow.meta?.updatedAt || new Date(0).toISOString(),
nodeCount: flow.nodes.length
});
} catch {
// 忽略损坏文件,避免一个坏流程阻塞列表
}
}
return rows.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
}
export function getFlow(id: string): FlowDefinition {
const raw = fs.readFileSync(fileOf(id), "utf8");
const flow = JSON.parse(raw) as FlowDefinition;
if (!flow || flow.schemaVersion !== 1 || !Array.isArray(flow.nodes) || !Array.isArray(flow.edges)) {
throw new Error(`invalid flow file: ${id}`);
}
return flow;
}
export function saveFlow(input: FlowDefinition): FlowDefinition {
if (!input || input.schemaVersion !== 1) throw new Error("invalid flow schemaVersion");
if (!Array.isArray(input.nodes) || !Array.isArray(input.edges)) throw new Error("invalid flow structure");
if (input.nodes.length > 2000) throw new Error("flow has too many nodes");
const now = new Date().toISOString();
const flow: FlowDefinition = {
...input,
meta: {
createdAt: input.meta?.createdAt || now,
updatedAt: now,
appVersion: app.getVersion()
}
};
const target = fileOf(flow.id);
const temp = `${target}.tmp`;
fs.writeFileSync(temp, JSON.stringify(flow, null, 2), "utf8");
fs.renameSync(temp, target);
return flow;
}
export function deleteFlow(id: string): void {
const target = fileOf(id);
if (fs.existsSync(target)) fs.unlinkSync(target);
}