From 303560f0f842b35b7603bb843f1059eedf5e1760 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Thu, 29 Oct 2026 09:21:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20P1=20=E6=8E=A5=E5=85=A5=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=BA=BA=E6=9C=BA=E4=BA=A4=E4=BA=92=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=EF=BC=889=20id=EF=BC=89=E3=80=81Time=20=E5=AE=B6=E6=97=8F?= =?UTF-8?q?=EF=BC=887=20id=EF=BC=89=E3=80=81=E5=9B=BE=E8=A1=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=AF=BB=E5=8F=96=EF=BC=882=20id=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/capabilities/human-dialog.ts | 89 +++++++++++++++++++++++++++ src/main/capabilities/index.ts | 2 + src/main/capabilities/time-caps.ts | 15 +++++ src/main/capabilities/web-batch2.ts | 19 +++++- 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/main/capabilities/human-dialog.ts create mode 100644 src/main/capabilities/time-caps.ts diff --git a/src/main/capabilities/human-dialog.ts b/src/main/capabilities/human-dialog.ts new file mode 100644 index 0000000..2741651 --- /dev/null +++ b/src/main/capabilities/human-dialog.ts @@ -0,0 +1,89 @@ +import { BrowserWindow } from "electron"; +import { register } from "../engine/registry"; +import { RuntimeError } from "../engine/types"; + +interface FormFieldSpec { key?: string; name?: string; label?: string; type?: string; value?: string; options?: unknown[] } + +function parseFields(raw: unknown, nodeId: string): FormFieldSpec[] { + if (raw === undefined || raw === null || raw === "") { + throw new RuntimeError("validation_error", "表单配置 jsonlist 为空", { nodeId, componentId: nodeId, recommendation: "填写 JSON 数组,例如 [{\"key\":\"name\",\"label\":\"名称\",\"type\":\"text\"}]" }); + } + if (Array.isArray(raw)) return raw as FormFieldSpec[]; + const text = String(raw).trim(); + if (text.startsWith("[")) { + try { + const parsed = JSON.parse(text) as unknown; + if (Array.isArray(parsed)) return parsed as FormFieldSpec[]; + } catch { /* fall through */ } + } + throw new RuntimeError("validation_error", "表单配置 jsonlist 无效", { nodeId, componentId: nodeId, recommendation: "填写 JSON 数组,例如 [{\"key\":\"name\",\"label\":\"名称\",\"type\":\"text\"}]" }); +} + +function fieldKey(f: FormFieldSpec, index: number): string { + return String(f.key ?? f.name ?? f.label ?? "field" + (index + 1)); +} + +function escapeHtml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +async function showForm(options: { title: string; fields: FormFieldSpec[]; timeoutSec: number }): Promise> { + return new Promise((resolve) => { + const rows = options.fields.map((f, i) => { + const key = escapeHtml(fieldKey(f, i)); + const label = escapeHtml(f.label ?? fieldKey(f, i)); + const value = escapeHtml(String(f.value ?? "")); + const type = String(f.type ?? "text").toLowerCase(); + if (type === "select" || (Array.isArray(f.options) && f.options.length)) { + const opts = (f.options ?? []).map(o => { + const ov = typeof o === "string" ? o : String((o as { value?: string }).value ?? (o as { label?: string }).label ?? ""); + const ol = typeof o === "string" ? o : String((o as { label?: string }).label ?? ov); + return '"; + }).join(""); + return "
"; + } + if (type === "textarea") { + return "
"; + } + if (type === "checkbox") { + const checked = String(f.value ?? "") === "true" || String(f.value ?? "") === "1" ? " checked" : ""; + return '
"; + } + const inputType = type === "password" ? "password" : "text"; + return "
'; + }).join("\n"); + const html = '

' + escapeHtml(options.title) + "

" + rows + ''; + const win = new BrowserWindow({ + width: 480, height: 0, useContentSize: true, autoHideMenuBar: true, + title: options.title, alwaysOnTop: true, + webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true } + }); + let settled = false; + const timer = setInterval(async () => { + try { + const values = await win.webContents.executeJavaScript("window.__formValues || null", true); + if (values) { clearInterval(timer); win.destroy(); resolve(values as Record); } + } catch { clearInterval(timer); resolve({}); } + }, 250); + const timeout = setTimeout(() => { + try { void win.webContents.executeJavaScript("document.getElementById(\"ok\").click()"); } catch {} + }, Math.max(5, options.timeoutSec) * 1000); + win.on("closed", () => { clearInterval(timer); clearTimeout(timeout); resolve({}); }); + void win.loadURL("data:text/html;charset=utf-8," + encodeURIComponent(html)); + }); +} + +const DIALOG_IDS = [ + "Dialog_GetConfig", "Dialog_GetConfigV1", "Dialog_GetConfigV2", "Dialog_GetConfigV3", + "Dialog_GetConfigV4", "Dialog_GetConfigV5", "Dialog_GetConfigV6", "Dialog_GetConfigV7", "Dialog_GetConfigV8" +]; + +register(DIALOG_IDS, async (ctx, node, args) => { + const fields = parseFields(args.jsonlist, node.id); + const title = String(args.title ?? args.configName ?? "请输入"); + const timeoutSec = Number(args.wait_time ?? 30) || 30; + const values = await showForm({ title, fields, timeoutSec }); + ctx.log("已获取人机交互表单输入:" + JSON.stringify(values), "info", node.id); + return values; +}); + diff --git a/src/main/capabilities/index.ts b/src/main/capabilities/index.ts index 2d6d010..d8c9cc7 100644 --- a/src/main/capabilities/index.ts +++ b/src/main/capabilities/index.ts @@ -12,6 +12,8 @@ import "./desktop"; import "./desktop-uia"; import "./web-batch2"; import "./web-crawl"; +import "./human-dialog"; +import "./time-caps"; import "./pdf"; import "./word"; import "./word-extended"; diff --git a/src/main/capabilities/time-caps.ts b/src/main/capabilities/time-caps.ts new file mode 100644 index 0000000..6587c8a --- /dev/null +++ b/src/main/capabilities/time-caps.ts @@ -0,0 +1,15 @@ +import { register } from "../engine/registry"; + +function part(args: Record, get: (d: Date) => number): number { + const ts = Number(args.timestamp); + const d = Number.isFinite(ts) && ts > 0 ? new Date(ts) : new Date(); + return get(d); +} + +register("Time_Year", async (_ctx, _node, args) => part(args, d => d.getFullYear())); +register("Time_Month", async (_ctx, _node, args) => part(args, d => d.getMonth() + 1)); +register("Time_Day", async (_ctx, _node, args) => part(args, d => d.getDate())); +register("Time_Hour", async (_ctx, _node, args) => part(args, d => d.getHours())); +register("Time_Minute", async (_ctx, _node, args) => part(args, d => d.getMinutes())); +register("Time_Second", async (_ctx, _node, args) => part(args, d => d.getSeconds())); +register("Time_WeekDay", async (_ctx, _node, args) => part(args, d => { const w = d.getDay(); return w === 0 ? 7 : w; })); diff --git a/src/main/capabilities/web-batch2.ts b/src/main/capabilities/web-batch2.ts index c3de32e..be5553b 100644 --- a/src/main/capabilities/web-batch2.ts +++ b/src/main/capabilities/web-batch2.ts @@ -90,4 +90,21 @@ register("Element_StopAutoClosePopup", async (ctx, node) => { return { active: false, stopped }; }); - +register(["Element_GetLineBarChartData", "Element_GetPieChartData"], async (ctx, node, args) => { + const browser = await browserSession(); + const selector = String((typeof args.uiSelector === "object" && args.uiSelector !== null ? (args.uiSelector as { value?: string }).value : args.uiSelector) ?? "body").replace(/^css:/, "").trim() || "body"; + const script = "(function(){" + + "if (!window.echarts) return [];" + + "var sel = " + JSON.stringify(selector) + ";" + + "var dom = document.querySelector(sel);" + + "var inst = dom ? window.echarts.getInstanceByDom(dom) : null;" + + "if (!inst) { var all = document.querySelectorAll('[_echarts_instance_]'); for (var i = 0; i < all.length; i++) { inst = window.echarts.getInstanceByDom(all[i]); if (inst) break; } }" + + "if (!inst) return [];" + + "var opt = inst.getOption(); var out = [];" + + "(opt.series || []).forEach(function(s) { out.push({ name: s.name || '', type: s.type || '', data: s.data || [] }); });" + + "return out;" + + "})()"; + const data = await browser.runJs(script); + ctx.log("已获取图表数据:" + JSON.stringify(data).slice(0, 120), "info", node.id); + return data; +});