diff --git a/resources/catalog.json b/resources/catalog.json index aef8b09..d9f9c8e 100644 --- a/resources/catalog.json +++ b/resources/catalog.json @@ -10,7 +10,7 @@ "vendor": 997, "native": 16, "triggers": 3, - "fields": 4293 + "fields": 4297 }, "groups": [ { @@ -11482,6 +11482,19 @@ "description": "屏幕上可见的目标文本,运行时截屏后通过 OCR 定位(填按钮或界面上的文字,如「登录」)", "advanced": false, "valueTypes": [] + }, + { + "key": "searchRegion", + "label": "搜索区域", + "widget": "region", + "type": "object", + "displayType": "region", + "required": false, + "defaultValue": "", + "placeholder": "", + "description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找", + "advanced": false, + "valueTypes": [] } ], "output": { @@ -11542,6 +11555,19 @@ "advanced": false, "valueTypes": [] }, + { + "key": "searchRegion", + "label": "搜索区域", + "widget": "region", + "type": "object", + "displayType": "region", + "required": false, + "defaultValue": "", + "placeholder": "", + "description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找", + "advanced": false, + "valueTypes": [] + }, { "key": "text", "label": "文本", @@ -11608,6 +11634,19 @@ "advanced": false, "valueTypes": [] }, + { + "key": "searchRegion", + "label": "搜索区域", + "widget": "region", + "type": "object", + "displayType": "region", + "required": false, + "defaultValue": "", + "placeholder": "", + "description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找", + "advanced": false, + "valueTypes": [] + }, { "key": "offsetX", "label": "x轴偏移量", @@ -11688,6 +11727,19 @@ "advanced": false, "valueTypes": [] }, + { + "key": "searchRegion", + "label": "搜索区域", + "widget": "region", + "type": "object", + "displayType": "region", + "required": false, + "defaultValue": "", + "placeholder": "", + "description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找", + "advanced": false, + "valueTypes": [] + }, { "key": "clickway", "label": "点击类型", diff --git a/src/main/capabilities/cv-vision.test.ts b/src/main/capabilities/cv-vision.test.ts index f5ece19..125cbfe 100644 --- a/src/main/capabilities/cv-vision.test.ts +++ b/src/main/capabilities/cv-vision.test.ts @@ -2,7 +2,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const powershellMock = vi.hoisted(() => vi.fn(async () => '{"ok":true}')); const detectMock = vi.hoisted(() => vi.fn(async () => ({ - texts: [{ text: "确认保存", box: [[0, 0], [20, 0], [20, 10], [0, 10]] }] + texts: [ + { text: "确认保存", box: [[0, 0], [20, 0], [20, 10], [0, 10]] }, + { text: "确认", box: [[100, 200], [130, 200], [130, 210], [100, 210]] } + ] }))); vi.mock("./desktop", () => ({ powershell: powershellMock })); @@ -47,4 +50,21 @@ describe("CV vision capabilities", () => { }, {} as any)).resolves.toBe("确认保存"); expect(powershellMock).toHaveBeenCalledTimes(1); }); + + it("restricts matching to the search region when provided", async () => { + // 屏幕上有两处「确认」: 区域(0,0,50,50)内只应命中 (5,5) 那处, 而不是第一个全屏命中 (115,205) + await expect(getHandler("CV_MouseClick")!({ log: vi.fn() } as any, { id: "n3", componentId: "CV_MouseClick" } as any, { + cvSelector: "确认", + searchRegion: JSON.stringify({ x: 0, y: 0, width: 50, height: 50 }) + }, {} as any)).resolves.toBe(true); + const script = String((powershellMock.mock.calls as unknown as Array<[string]>)[1]?.[0] ?? ""); + expect(script).toContain("SetCursorPos(10, 5)"); + }); + + it("rejects when target is outside the search region", async () => { + await expect(getHandler("CV_MouseClick")!({ log: vi.fn() } as any, { id: "n4", componentId: "CV_MouseClick" } as any, { + cvSelector: "确认", + searchRegion: JSON.stringify({ x: 500, y: 500, width: 50, height: 50 }) + }, {} as any)).rejects.toThrow("未找到目标文本"); + }); }); diff --git a/src/renderer/src/panels/Properties.tsx b/src/renderer/src/panels/Properties.tsx index 7c43473..ced2a20 100644 --- a/src/renderer/src/panels/Properties.tsx +++ b/src/renderer/src/panels/Properties.tsx @@ -772,12 +772,25 @@ function FieldControl({ placeholder={field.placeholder || ""} onChange={(event) => onChange(field.key, event.target.value)} /> + ) : field.widget === "region" ? ( +
+ onChange(field.key, event.target.value)} + /> + + +
) : (
onChange(field.key, event.target.value)} />