feat: P1 接入——人机交互表单(9 id)、Time 家族(7 id)、图表数据读取(2 id)
This commit is contained in:
parent
b08f056aba
commit
303560f0f8
89
src/main/capabilities/human-dialog.ts
Normal file
89
src/main/capabilities/human-dialog.ts
Normal file
@ -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, ">").replace(/"/g, """);
|
||||
}
|
||||
|
||||
async function showForm(options: { title: string; fields: FormFieldSpec[]; timeoutSec: number }): Promise<Record<string, string>> {
|
||||
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 '<option value="' + escapeHtml(ov) + '"' + (ov === String(f.value ?? "") ? " selected" : "") + ">" + escapeHtml(ol) + "</option>";
|
||||
}).join("");
|
||||
return "<label>" + label + '<br/><select data-key="' + key + '">' + opts + "</select></label><br/>";
|
||||
}
|
||||
if (type === "textarea") {
|
||||
return "<label>" + label + '<br/><textarea data-key="' + key + '" rows="3">' + value + "</textarea></label><br/>";
|
||||
}
|
||||
if (type === "checkbox") {
|
||||
const checked = String(f.value ?? "") === "true" || String(f.value ?? "") === "1" ? " checked" : "";
|
||||
return '<label><input type="checkbox" data-key="' + key + '"' + checked + " /> " + label + "</label><br/>";
|
||||
}
|
||||
const inputType = type === "password" ? "password" : "text";
|
||||
return "<label>" + label + '<br/><input type="' + inputType + '" data-key="' + key + '" value="' + value + '" /></label><br/>';
|
||||
}).join("\n");
|
||||
const html = '<!DOCTYPE html><html><head><meta charset="utf-8"><style>body{font-family:Microsoft YaHei,sans-serif;margin:16px;font-size:14px;color:#1f2937}label{display:block;margin-bottom:10px}input,select,textarea{width:100%;box-sizing:border-box;padding:6px;margin-top:4px}#ok{margin-top:8px;padding:8px 24px;background:#2563eb;color:#fff;border:none;border-radius:6px;cursor:pointer}</style></head><body><h3>' + escapeHtml(options.title) + "</h3>" + rows + '<button id="ok">确认</button><script>document.getElementById("ok").onclick=function(){var out={};document.querySelectorAll("[data-key]").forEach(function(el){out[el.dataset.key]=el.type==="checkbox"?el.checked:el.value});window.__formValues=out;};</script></body></html>';
|
||||
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<string, string>); }
|
||||
} 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;
|
||||
});
|
||||
|
||||
@ -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";
|
||||
|
||||
15
src/main/capabilities/time-caps.ts
Normal file
15
src/main/capabilities/time-caps.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { register } from "../engine/registry";
|
||||
|
||||
function part(args: Record<string, unknown>, 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; }));
|
||||
@ -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;
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user