feat: 画布式 RPA 桌面端 Day1(1016 目录/画布/容器/属性/持久化)
This commit is contained in:
commit
32ca0a1af8
34 files changed
+15141
No files matched your search
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user