feat: 接入网页批二——文件上传/多标签/窗口状态/停止加载/相似元素数组/弹窗自动关闭
This commit is contained in:
parent
9b8bc0eb1a
commit
560134171f
@ -768,6 +768,58 @@ export class BrowserSession implements BrowserLike {
|
||||
return result.items;
|
||||
}
|
||||
|
||||
maximize(): void {
|
||||
if (!this.win.isDestroyed()) this.win.maximize();
|
||||
}
|
||||
|
||||
minimize(): void {
|
||||
if (!this.win.isDestroyed()) this.win.minimize();
|
||||
}
|
||||
|
||||
stopLoading(): void {
|
||||
if (!this.win.isDestroyed()) this.win.webContents.stop();
|
||||
}
|
||||
|
||||
async uploadFileToInput(selector: string, filePath: string): Promise<number> {
|
||||
const doc = await this.cdp<{ root?: { nodeId?: number } }>("DOM.getDocument");
|
||||
const rootNodeId = doc.root?.nodeId;
|
||||
if (!rootNodeId) throw new RuntimeError("browser_error", "无法定位页面 DOM 根节点");
|
||||
const css = selector.replace(/^css:/, "").trim();
|
||||
const found = await this.cdp<{ nodeId?: number }>("DOM.querySelector", { nodeId: rootNodeId, selector: css });
|
||||
if (!found.nodeId) {
|
||||
throw new RuntimeError("element_not_found", "未找到文件输入元素:" + css, { recommendation: "确认 input[type=file] 存在且已渲染" });
|
||||
}
|
||||
await this.cdp("DOM.setFileInputFiles", { files: [filePath], nodeId: found.nodeId });
|
||||
return found.nodeId;
|
||||
}
|
||||
|
||||
startAutoCloseDialogs(): boolean {
|
||||
if (!this.win.isDestroyed() && !this.win.webContents.debugger.isAttached()) this.win.webContents.debugger.attach("1.3");
|
||||
this.cdp("Page.enable").catch(() => {});
|
||||
const flags = globalThis as { __rpaAutoCloseDialogs?: boolean; __rpaAutoCloseDialogListener?: unknown };
|
||||
if (flags.__rpaAutoCloseDialogs) return false;
|
||||
flags.__rpaAutoCloseDialogs = true;
|
||||
const listener = (_event: unknown, method: string) => {
|
||||
if (method === "Page.javascriptDialogOpening") {
|
||||
this.cdp("Page.handleJavaScriptDialog", { accept: true }).catch(() => {});
|
||||
}
|
||||
};
|
||||
flags.__rpaAutoCloseDialogListener = listener;
|
||||
this.win.webContents.debugger.on("message", listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
stopAutoCloseDialogs(): boolean {
|
||||
const flags = globalThis as { __rpaAutoCloseDialogs?: boolean; __rpaAutoCloseDialogListener?: unknown };
|
||||
if (!flags.__rpaAutoCloseDialogs) return false;
|
||||
if (flags.__rpaAutoCloseDialogListener) {
|
||||
this.win.webContents.debugger.removeListener("message", flags.__rpaAutoCloseDialogListener as never);
|
||||
}
|
||||
flags.__rpaAutoCloseDialogs = false;
|
||||
flags.__rpaAutoCloseDialogListener = undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
async currentUrl(): Promise<string> {
|
||||
return this.win.webContents.getURL();
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ function selectorOf(args: Record<string, unknown>): unknown {
|
||||
return selector;
|
||||
}
|
||||
|
||||
register(["WebBrowser_CreateV3", "WebBrowser_Create", "WebBrowser_CreateBackground"], async (ctx, node, args) => {
|
||||
register(["WebBrowser_CreateV3", "WebBrowser_CreateV2", "WebBrowser_Create", "WebBrowser_CreateBackground"], async (ctx, node, args) => {
|
||||
const browser = await session(ctx);
|
||||
const url = String(args.url ?? "");
|
||||
if (url) {
|
||||
|
||||
@ -10,6 +10,7 @@ import "./browser-control";
|
||||
import "./office";
|
||||
import "./desktop";
|
||||
import "./desktop-uia";
|
||||
import "./web-batch2";
|
||||
import "./pdf";
|
||||
import "./word";
|
||||
import "./word-extended";
|
||||
|
||||
74
src/main/capabilities/web-batch2.ts
Normal file
74
src/main/capabilities/web-batch2.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { browserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
|
||||
function cssValue(selector: unknown): string {
|
||||
const raw = typeof selector === "object" && selector !== null
|
||||
? String((selector as { value?: string }).value ?? "")
|
||||
: String(selector ?? "");
|
||||
const text = raw.trim();
|
||||
return text.startsWith("css:") ? text.slice(4).trim() : text;
|
||||
}
|
||||
|
||||
function requireUploadPath(args: Record<string, unknown>): string {
|
||||
const filePath = String(args.file_path ?? args.filePath ?? args.path ?? "").trim();
|
||||
const folder = String(args.folder_path ?? args.folder ?? "").trim();
|
||||
const target = filePath || folder;
|
||||
if (!target) {
|
||||
throw new RuntimeError("validation_error", "上传文件路径为空", { recommendation: "填写要上传的本地文件完整路径" });
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
register(["Element_FileUploader", "WebBrowser_Upload", "WebBrowser_UploadDialog"], async (ctx, node, args) => {
|
||||
const browser = await browserSession();
|
||||
const selector = cssValue(args.uiSelector ?? "css:input[type=file]") || "input[type=file]";
|
||||
const filePath = requireUploadPath(args);
|
||||
const nodeId = await browser.uploadFileToInput(selector, filePath);
|
||||
ctx.log("已设置上传文件:" + filePath, "info", node.id);
|
||||
return { uploaded: true, selector, filePath, nodeId };
|
||||
});
|
||||
|
||||
register(["WebBrowser_GetTAB", "WebBrowser_GetTAB_V1", "WebBrowser_GetTAB_V2"], async (ctx, node) => {
|
||||
const browser = await browserSession();
|
||||
const url = await browser.currentUrl();
|
||||
ctx.log("已获取标签页列表(单窗口模式)", "info", node.id);
|
||||
return [{ index: 0, url, active: true }];
|
||||
});
|
||||
|
||||
register("WebBrowser_Switch_V1", async (ctx, node, args) => {
|
||||
const browser = await browserSession();
|
||||
const url = await browser.currentUrl();
|
||||
ctx.log("运行浏览器为单窗口模式,当前即唯一标签页", "info", node.id);
|
||||
return { switched: true, index: 0, url, active: String(args.is_active ?? "1") === "1" };
|
||||
});
|
||||
|
||||
register("WebBrowser_Maximize", async (ctx, node) => {
|
||||
browser.maximize();
|
||||
return true;
|
||||
});
|
||||
|
||||
register("WebBrowser_Minimize", async (ctx, node) => {
|
||||
browser.minimize();
|
||||
return true;
|
||||
});
|
||||
|
||||
register("WebBrowser_Stop", async (ctx, node) => {
|
||||
browser.stopLoading();
|
||||
return true;
|
||||
});
|
||||
|
||||
register(["Element_ElementAarray", "Element_ElementCaptureSimilar"], async (ctx, node, args) => {
|
||||
const browser = await browserSession();
|
||||
const selector = args.uiSelector ?? args.selector ?? args.target;
|
||||
if (!selector) {
|
||||
throw new RuntimeError("validation_error", "元素选择器为空", { nodeId: node.id, componentId: node.componentId, recommendation: "请拾取或填写相似元素" });
|
||||
}
|
||||
const references = await browser.getSimilarElements(selector, {
|
||||
start: Number.isFinite(Number(args.start_index)) ? Math.trunc(Number(args.start_index)) : 1,
|
||||
end: Number.isFinite(Number(args.end_index)) ? Math.trunc(Number(args.end_index)) : -1,
|
||||
reverse: String(args.reverse ?? "") === "1" || String(args.reverse ?? "") === "true"
|
||||
});
|
||||
ctx.log("已获取 " + references.length + " 个相似元素(数组)", "info", node.id);
|
||||
return references;
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user