ms-vite-plugin 1.4.40 → 1.4.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,7 +75,7 @@ function recognizeAbs(
75
75
  y?: number,
76
76
  ex?: number,
77
77
  ey?: number,
78
- confidenceThreshold?: number,
78
+ confidenceThreshold?: number
79
79
  ): OCRChar[];
80
80
  ```
81
81
 
@@ -85,7 +85,7 @@ function recognizeAbs(
85
85
  | `input` | string | 是 | | 输入源,支持 `"screen"`、图片文件路径、URL 或 imageId |
86
86
  | `x`, `y` | number | 否 | 0 | 裁剪区域左上角坐标 |
87
87
  | `ex`, `ey` | number | 否 | 0 | 裁剪区域右下角坐标;`ex` 或 `ey` 为 0 时分别使用图像宽度或高度 |
88
- | `confidenceThreshold` | number | 否 | 0.8 | 相似度下限;`<=0` 时按 `0.8`;低于阈值的字符会被过滤;`>0.95` 时要求模板与图像像素完全一致 |
88
+ | `confidenceThreshold` | number | 否 | 0.8 | 相似度下限;`<=0` 时按 `0.8`;低于阈值的字符会被过滤;`>0.95` 时要求像素完全一致 |
89
89
 
90
90
  **返回值:**`OCRChar[]`。按行从上到下、同一行从左到右排列。颜色按字库各字自带的前景色/偏色匹配,无需额外传色。
91
91
 
@@ -108,7 +108,7 @@ function findTextAbs(
108
108
  ex?: number,
109
109
  ey?: number,
110
110
  confidenceThreshold?: number,
111
- exactMatch?: boolean,
111
+ exactMatch?: boolean
112
112
  ): OCRChar[];
113
113
  ```
114
114
 
@@ -120,7 +120,7 @@ function findTextAbs(
120
120
  | `x`, `y` | number | 否 | 0 | 裁剪区域左上角坐标 |
121
121
  | `ex`, `ey` | number | 否 | 0 | 裁剪区域右下角坐标;`ex` 或 `ey` 为 0 时分别使用图像宽度或高度 |
122
122
  | `confidenceThreshold` | number | 否 | 0.8 | 单字符识别相似度下限;语义同 `recognizeAbs` |
123
- | `exactMatch` | boolean | 否 | false | `false` 为行内子串包含匹配;`true` 要求某一整行文本完全等于目标字符串 |
123
+ | `exactMatch` | boolean | 否 | false | `false` 为行内子串包含匹配;`true` 要求某一整行文本完全等于目标字符串 |
124
124
 
125
125
  **返回值:**命中结果数组。每个目标最多返回一次命中(最先匹配到的行);每项的 `text` 是目标字符串。
126
126
 
package/docs/api/node.md CHANGED
@@ -84,6 +84,13 @@ interface NodeInfo {
84
84
  clickCenter(): boolean; // 点击中心
85
85
  clickRandom(): boolean; // 随机点击
86
86
  hittable(): boolean; // 节点是否可接收事件,用于判断节点是否在屏幕可见
87
+ tap(): boolean; // 无障碍点击
88
+ doubleTap(): boolean; // 无障碍双击
89
+ press(duration: number): boolean; // 无障碍长按,duration 单位毫秒
90
+ scrollToVisible(): boolean; // 将节点滚动到可见区域
91
+ clearInput(): boolean; // 清空输入
92
+ input(text: string): boolean; // 追加输入(聚焦后输入)
93
+ setValue(text: string): boolean; // 设置节点的值(见下方支持控件)
87
94
  parent(): NodeInfo | null; // 获取父节点
88
95
  child(index: number): NodeInfo | null; // 获取子节点
89
96
  allChildren(): NodeInfo[]; // 获取所有子节点
@@ -263,16 +270,81 @@ selector.releaseNode();
263
270
  #### 点击操作
264
271
 
265
272
  ```typescript
266
- // 点击节点中心
273
+ // 点击节点中心(坐标点击)
267
274
  node.clickCenter(): boolean;
268
275
 
269
- // 随机点击节点区域
276
+ // 随机点击节点区域(坐标点击)
270
277
  node.clickRandom(): boolean;
271
278
 
272
279
  // 节点是否可接收事件(用于判断是否显示在屏幕上)
273
280
  node.hittable(): boolean;
281
+
282
+ // 无障碍点击
283
+ node.tap(): boolean;
284
+
285
+ // 无障碍双击
286
+ node.doubleTap(): boolean;
287
+
288
+ // 无障碍长按,duration 单位毫秒
289
+ node.press(duration: number): boolean;
290
+
291
+ // 将节点滚动到可见区域
292
+ node.scrollToVisible(): boolean;
293
+
294
+ // 清空输入
295
+ node.clearInput(): boolean;
296
+
297
+ // 追加输入
298
+ node.input(text: string): boolean;
299
+
300
+ // 设置节点的值
301
+ node.setValue(text: string): boolean;
302
+ ```
303
+
304
+ #### tap / doubleTap / press - 无障碍手势
305
+
306
+ 通过无障碍 API 直接操作节点,不依赖坐标点击。
307
+
308
+ | 方法 | 参数 | 说明 |
309
+ | --- | --- | --- |
310
+ | `tap()` | 无 | 无障碍单击 |
311
+ | `doubleTap()` | 无 | 无障碍双击 |
312
+ | `press(duration)` | `duration`:长按持续时间,单位毫秒 | 无障碍长按 |
313
+
314
+ **示例:**
315
+
316
+ ```typescript
317
+ const selector = createNodeSelector();
318
+
319
+ const btn = selector.label("确定").type("XCUIElementTypeButton").getOneNodeInfo(3000);
320
+ if (btn) {
321
+ // 无障碍单击
322
+ btn.tap();
323
+
324
+ // 无障碍双击
325
+ btn.doubleTap();
326
+
327
+ // 无障碍长按 800ms
328
+ btn.press(800);
329
+ }
330
+
331
+ selector.releaseNode();
274
332
  ```
275
333
 
334
+ #### setValue - 设置节点的值
335
+
336
+ 按控件类型自动处理,常用支持如下:
337
+
338
+ | 控件类型 | 传参示例 | 说明 |
339
+ | --- | --- | --- |
340
+ | `XCUIElementTypePickerWheel` | `"1小时"` | 滚轮选到对应选项文案 |
341
+ | `XCUIElementTypeTextField` | `"hello"` | 先聚焦再输入文本 |
342
+ | `XCUIElementTypeSecureTextField` | `"123456"` | 同上 |
343
+ | `XCUIElementTypeTextView` | `"多行内容"` | 同上 |
344
+ | `XCUIElementTypeSearchField` | `"关键词"` | 同上 |
345
+
346
+ 文本类控件不会先清空已有内容;需要清空时先调用 `clearInput()`。
347
+
276
348
  **示例:**
277
349
 
278
350
  ```typescript
@@ -288,12 +360,27 @@ if (confirmBtn) {
288
360
  const isHittable = confirmBtn.hittable();
289
361
  logi(`是否可接收事件: ${isHittable}`);
290
362
 
291
- const clicked = confirmBtn.clickCenter();
363
+ confirmBtn.scrollToVisible();
364
+ // 优先使用无障碍点击;也可用 clickCenter / clickRandom 做坐标点击
365
+ const clicked = confirmBtn.tap();
292
366
  if (clicked) {
293
367
  logi("成功点击确定按钮");
294
368
  }
295
369
  }
296
370
 
371
+ // 文本框:可先清空再设值,或直接追加输入
372
+ const field = selector.type("XCUIElementTypeTextField").getOneNodeInfo(3000);
373
+ if (field) {
374
+ field.clearInput();
375
+ field.setValue("hello");
376
+ }
377
+
378
+ // 滚轮:传界面上的选项文案
379
+ const wheel = selector.type("XCUIElementTypePickerWheel").getOneNodeInfo(3000);
380
+ if (wheel) {
381
+ wheel.setValue("1小时");
382
+ }
383
+
297
384
  // 随机点击避免检测
298
385
  const randomClick = confirmBtn?.clickRandom();
299
386
 
@@ -5,7 +5,7 @@
5
5
  注意:
6
6
 
7
7
  - Mat 用字符串句柄表示,形如 `$CV_MAT$_…`。
8
- - 多数算子返回新句柄;`rectangle` / `circle` / `line` / `putText` / `fillPoly` / `polylines` / `drawContours` / `floodFill` / `ellipse` / `drawMarker` / `arrowedLine` / `fillConvexPoly` / `insertChannel` / `watershed` / `grabCut` 等会就地修改输入 Mat(或 options 中指定的目标句柄)。
8
+ - 多数算子返回新句柄;`rectangle` / `circle` / `line` / `putText` / `fillPoly` / `polylines` / `drawContours` / `floodFill` / `ellipse` / `drawMarker` / `arrowedLine` / `fillConvexPoly` 等会就地修改输入 Mat(或 options 中指定的目标句柄)。
9
9
  - 与 `image` 可通过 `fromImageId` / `toImageId` 互通(独立拷贝)。
10
10
  - 路径 / msbundle / imageId / `screen` 等读图只用 `imread`;其它 API 只接受 Mat 句柄;`imdecode` 只解 base64。
11
11
  - 用完请 `release` / `releaseAll`(同时存活句柄上限约 32)。
@@ -17,17 +17,16 @@
17
17
  | --- | --- |
18
18
  | 读图 / 截屏 | `capture` · `imread` · `imdecode` · `imwrite` · `imencode` · `fromImageId` / `toImageId` |
19
19
  | Mat | `Mat` / `zeros` / `ones` · `clone` · `empty` / `isRelease` · `release` / `releaseAll` · `getSize` / `at` / `set` · `copyTo` |
20
- | 颜色 / 阈值 | `cvtColor` · `threshold` · `adaptiveThreshold` · `inRange` · `CLAHE` / `equalizeHist` · `applyColorMap` · `decolor` |
21
- | 算术 / 位运算 | `add` / `subtract` / `multiply` / `divide` · bitwise_* · `absdiff` · `addWeighted` · `convertScaleAbs` · `normalize` / `norm` · `meanStdDev` · `findNonZero` · `countNonZero` · `minMaxLoc` / `mean` / `sum` · `split` / `merge` · `hconcat` / `vconcat` · `LUT` · `compare` · `min` / `max` · `extractChannel` / `insertChannel` · `mixChannels` · `pow` / `sqrt` / `exp` / `log` · `magnitude` · `cartToPolar` / `polarToCart` · `PSNR` · `scaleAdd` |
22
- | 滤波边缘 | `GaussianBlur` / `blur` / `medianBlur` / `bilateralFilter` / `boxFilter` / `stackBlur` · `Canny` / `Sobel` / `Laplacian` / `Scharr` · `filter2D` / `sepFilter2D` · `pyrDown` / `pyrUp` · `edgePreservingFilter` · `detailEnhance` · `pencilSketch` · `stylization` · `fastNlMeansDenoising` / `fastNlMeansDenoisingColored` · `pyrMeanShiftFiltering` |
23
- | 几何 | `resize` / `crop` / `rotate` / `flip` · `copyMakeBorder` · `warpAffine` / `warpPerspective` / `warpPolar` · `remap` · `getRotationMatrix2D` · `getAffineTransform` · `getPerspectiveTransform` · `invertAffineTransform` · `findHomography` · `perspectiveTransform` · `estimateAffine2D` / `estimateAffinePartial2D` · `getRectSubPix` · `phaseCorrelate` |
20
+ | 颜色 / 阈值 | `cvtColor` · `threshold` · `adaptiveThreshold` · `inRange` · `CLAHE` / `equalizeHist` |
21
+ | 算术 / 位运算 | `add` / `subtract` / `multiply` / `divide` · bitwise_* · `absdiff` · `addWeighted` · `convertScaleAbs` · `normalize` / `norm` · `meanStdDev` · `findNonZero` · `countNonZero` · `minMaxLoc` / `mean` / `sum` · `split` / `merge` · `hconcat` / `vconcat` · `LUT` · `compare` · `min` / `max` · `extractChannel` |
22
+ | 滤波边缘 | `GaussianBlur` / `blur` / `medianBlur` / `bilateralFilter` / `boxFilter` · `Canny` / `Sobel` / `Laplacian` / `Scharr` · `filter2D` · `pyrDown` / `pyrUp` · `fastNlMeansDenoising` / `fastNlMeansDenoisingColored` · `inpaint` |
23
+ | 几何 | `resize` / `crop` / `rotate` / `flip` · `copyMakeBorder` · `warpAffine` / `warpPerspective` · `getRotationMatrix2D` · `getAffineTransform` · `getPerspectiveTransform` · `invertAffineTransform` · `findHomography` · `perspectiveTransform` · `estimateAffine2D` / `estimateAffinePartial2D` · `getRectSubPix` · `phaseCorrelate` |
24
24
  | 绘制 | `rectangle` / `circle` / `line` / `ellipse` / `arrowedLine` / `drawMarker` · `putText` / `fillPoly` / `fillConvexPoly` / `polylines` / `drawContours` · `getTextSize` |
25
- | 匹配 | `matchTemplate` / `matchTemplateLoc` · `SIFT` / `ORB` / `AKAZE` / `BRISK` / `KAZE` / `FAST` / `AGAST` / `MSER` / `SimpleBlobDetector` · `BFMatcher` / `knnMatch` · `FlannBasedMatcher` / `flannKnnMatch` · `drawKeypoints` / `drawMatches` |
25
+ | 匹配 | `matchTemplate` / `matchTemplateLoc` · `SIFT` / `ORB` / `FAST` · `BFMatcher` / `knnMatch` · `FlannBasedMatcher` / `flannKnnMatch` · `drawKeypoints` / `drawMatches` |
26
26
  | 轮廓 | `findContours` · `boundingRect` / `minAreaRect` / `boxPoints` · `contourArea` / `arcLength` / `approxPolyDP` / `convexHull` / `convexityDefects` / `isContourConvex` · `moments` / `HuMoments` / `matchShapes` · `pointPolygonTest` · `fitEllipse` / `fitLine` / `minEnclosingCircle` |
27
- | 霍夫 / 连通域 / 分割 | `HoughLines` / `HoughLinesP` / `HoughCircles` / `LSD` · `connectedComponents` / `connectedComponentsWithStats` · `distanceTransform` · `floodFill` · `goodFeaturesToTrack` / `cornerHarris` / `cornerSubPix` / `cornerMinEigenVal` / `preCornerDetect` · `spatialGradient` · `watershed` / `grabCut` · `integral` |
27
+ | 霍夫 / 连通域 | `HoughLinesP` / `HoughCircles` · `connectedComponents` / `connectedComponentsWithStats` · `distanceTransform` · `floodFill` · `goodFeaturesToTrack` / `cornerHarris` / `cornerSubPix` |
28
28
  | 直方图 | `calcHist` · `compareHist` · `calcBackProject` |
29
- | Photo / 克隆 | `inpaint` · `seamlessClone` · `colorChange` · `illuminationChange` · `textureFlattening` |
30
- | 扫码 / 检测 | `detectQRCode` · `detectBarcode` · `encodeQRCode` · `CascadeClassifier` · `HOGDetect` |
29
+ | 扫码 | `encodeQRCode` · `detectQRCode` · `detectBarcode` |
31
30
 
32
31
  ---
33
32
 
@@ -906,37 +905,25 @@ const score = cv.compareHist(h1, h2, { method: cv.HISTCMP_CORREL });
906
905
  cv.release(h1); cv.release(h2);
907
906
  ```
908
907
 
909
- ### boxPoints / pointPolygonTest / minEnclosingCircle / fitEllipse
910
-
911
- ```typescript
912
- function boxPoints(options: { center: Point; size: Size | [number, number]; angle?: number }): Point[] | null;
913
- function pointPolygonTest(contour: Point[], ptOrOptions: Point | { pt: Point; measureDist?: boolean }): number;
914
- function minEnclosingCircle(points: Point[]): { center: Point; radius: number } | null;
915
- function fitEllipse(points: Point[]): RotatedRect | null;
916
- ```
917
-
918
- **示例:**
919
-
920
- ```javascript
921
- const rr = cv.minAreaRect(contour);
922
- const corners = cv.boxPoints(rr);
923
- const inside = cv.pointPolygonTest(contour, { pt: { x: 100, y: 100 } });
924
- ```
925
-
926
- ### detectQRCode / detectBarcode
908
+ ### encodeQRCode / detectQRCode / detectBarcode
927
909
 
928
910
  ```typescript
911
+ function encodeQRCode(text: string): Mat | null;
929
912
  function detectQRCode(mat: Mat): { decoded: string; points: Point[] } | null;
930
913
  function detectBarcode(mat: Mat): { decoded: string[]; types: string[]; points: Point[] } | null;
931
914
  ```
932
915
 
916
+ `encodeQRCode` 返回可显示/可识别的 BGR Mat(已放大并加静区),可直接交给 `detectQRCode`。
917
+
933
918
  **示例:**
934
919
 
935
920
  ```javascript
936
- const qr = cv.detectQRCode(src);
921
+ const qrMat = cv.encodeQRCode("https://example.com");
922
+ const qr = cv.detectQRCode(qrMat); // { decoded, points }
937
923
  if (qr) logi(qr.decoded);
938
924
  const codes = cv.detectBarcode(src);
939
925
  if (codes) logi(codes.decoded.join(","));
926
+ cv.release(qrMat);
940
927
  ```
941
928
 
942
929
  ### filter2D / ellipse / getTextSize
@@ -1054,30 +1041,6 @@ cv.release(a.descriptors);
1054
1041
  cv.release(b.descriptors);
1055
1042
  ```
1056
1043
 
1057
- ### AKAZE / BRISK / FAST
1058
-
1059
- ```typescript
1060
- function AKAZE(mat: Mat, options?: { mask?: Mat }): FeatureResult | null;
1061
- function BRISK(mat: Mat, options?: { thresh?: number; octaves?: number; mask?: Mat }): FeatureResult | null;
1062
- function FAST(mat: Mat, options?: { threshold?: number; nonmaxSuppression?: boolean; mask?: Mat }): { keypoints: KeyPoint[] } | null;
1063
- ```
1064
-
1065
- **说明:**
1066
-
1067
- - `AKAZE` / `BRISK` 返回关键点 + 描述子。
1068
- - `FAST` **只检测关键点**,无 `descriptors`。
1069
- - `BRISK`:`thresh` 默认 30,`octaves` 默认 3。
1070
- - `FAST`:`threshold` 默认 10,`nonmaxSuppression` 默认 `true`。
1071
-
1072
- **示例:**
1073
-
1074
- ```javascript
1075
- const fast = cv.FAST(gray, { threshold: 20 });
1076
- logi(`FAST count=${fast.keypoints.length}`);
1077
- const brisk = cv.BRISK(gray, { thresh: 30, octaves: 3 });
1078
- cv.release(brisk.descriptors);
1079
- ```
1080
-
1081
1044
  ### BFMatcher
1082
1045
 
1083
1046
  ```typescript
@@ -1235,75 +1198,6 @@ const d1 = cv.fastNlMeansDenoising(gray, { h: 3 });
1235
1198
  const d2 = cv.fastNlMeansDenoisingColored(src, { h: 3, hColor: 3 });
1236
1199
  ```
1237
1200
 
1238
- ### edgePreservingFilter / detailEnhance / stylization
1239
-
1240
- ```typescript
1241
- function edgePreservingFilter(mat: Mat, options?: { flags?: number; sigma_s?: number; sigma_r?: number }): Mat | null;
1242
- function detailEnhance(mat: Mat, options?: { sigma_s?: number; sigma_r?: number }): Mat | null;
1243
- function stylization(mat: Mat, options?: { sigma_s?: number; sigma_r?: number }): Mat | null;
1244
- ```
1245
-
1246
- ```javascript
1247
- const soft = cv.edgePreservingFilter(src, { flags: cv.RECURS_FILTER });
1248
- const sharp = cv.detailEnhance(src);
1249
- const art = cv.stylization(src);
1250
- ```
1251
-
1252
- ### pencilSketch / decolor
1253
-
1254
- ```typescript
1255
- function pencilSketch(mat: Mat, options?: {
1256
- sigma_s?: number; sigma_r?: number; shade_factor?: number;
1257
- }): { gray: Mat; color: Mat } | null;
1258
- function decolor(mat: Mat): { grayscale: Mat; colorBoost: Mat } | null;
1259
- ```
1260
-
1261
- 返回两个新句柄,用完需分别 `release`。
1262
-
1263
- ```javascript
1264
- const sketch = cv.pencilSketch(src);
1265
- const dc = cv.decolor(src);
1266
- cv.release(sketch.gray); cv.release(sketch.color);
1267
- cv.release(dc.grayscale); cv.release(dc.colorBoost);
1268
- ```
1269
-
1270
- ### seamlessClone
1271
-
1272
- ```typescript
1273
- function seamlessClone(src: Mat, options: {
1274
- dst: Mat; mask: Mat; p?: Point; x?: number; y?: number; flags?: number;
1275
- }): Mat | null;
1276
- ```
1277
-
1278
- | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
1279
- | --- | --- | --- | --- | --- |
1280
- | `dst` | Mat | 是 | - | 背景图 |
1281
- | `mask` | Mat | 是 | - | 前景有效区域 |
1282
- | `p` / `x,y` | Point | 是 | - | 贴入中心 |
1283
- | `flags` | number | 否 | `NORMAL_CLONE` | `NORMAL_CLONE` / `MIXED_CLONE` / `MONOCHROME_TRANSFER` |
1284
-
1285
- ```javascript
1286
- const blend = cv.seamlessClone(fg, { dst: bg, mask, p: { x: 200, y: 150 }, flags: cv.NORMAL_CLONE });
1287
- ```
1288
-
1289
- ### colorChange / illuminationChange / textureFlattening
1290
-
1291
- ```typescript
1292
- function colorChange(mat: Mat, options: { mask: Mat; red_mul?: number; green_mul?: number; blue_mul?: number }): Mat | null;
1293
- function illuminationChange(mat: Mat, options: { mask: Mat; alpha?: number; beta?: number }): Mat | null;
1294
- function textureFlattening(mat: Mat, options: {
1295
- mask: Mat; low_threshold?: number; high_threshold?: number; kernel_size?: number;
1296
- }): Mat | null;
1297
- ```
1298
-
1299
- ```javascript
1300
- const c = cv.colorChange(src, { mask, red_mul: 1.5, green_mul: 1.0, blue_mul: 0.8 });
1301
- const i = cv.illuminationChange(src, { mask, alpha: 0.2, beta: 0.4 });
1302
- const f = cv.textureFlattening(src, { mask });
1303
- ```
1304
-
1305
- ---
1306
-
1307
1201
  ## Core 扩展
1308
1202
 
1309
1203
  ### LUT / compare / min / max
@@ -1322,168 +1216,9 @@ const mask = cv.compare(a, { src2: b, cmpop: cv.CMP_GT });
1322
1216
  const lo = cv.min(a, { src2: b });
1323
1217
  ```
1324
1218
 
1325
- ### extractChannel / insertChannel / mixChannels
1326
-
1327
- ```typescript
1328
- function extractChannel(mat: Mat, options: { coi: number }): Mat | null;
1329
- /** 就地写入 dst,返回 dst 句柄 */
1330
- function insertChannel(srcChannel: Mat, options: { dst: Mat; coi: number }): Mat | null;
1331
- /** 就地改写 dst,成功返回 true */
1332
- function mixChannels(options: { src: Mat[]; dst: Mat[]; fromTo: number[] }): boolean;
1333
- ```
1334
-
1335
- ```javascript
1336
- const b = cv.extractChannel(src, { coi: 0 });
1337
- cv.insertChannel(b, { dst: out, coi: 0 }); // 就地改 out
1338
- cv.mixChannels({ src: [src], dst: [out], fromTo: [0, 2, 1, 1, 2, 0] });
1339
- ```
1340
-
1341
- ### pow / sqrt / exp / log / magnitude / cartToPolar / polarToCart
1342
-
1343
- ```typescript
1344
- function pow(mat: Mat, options: { power: number }): Mat | null;
1345
- function sqrt(mat: Mat): Mat | null;
1346
- function exp(mat: Mat): Mat | null;
1347
- function log(mat: Mat): Mat | null;
1348
- function magnitude(options: { x: Mat; y: Mat }): Mat | null;
1349
- function cartToPolar(options: { x: Mat; y: Mat; angleInDegrees?: boolean }): { magnitude: Mat; angle: Mat } | null;
1350
- function polarToCart(options: { magnitude: Mat; angle: Mat; angleInDegrees?: boolean }): { x: Mat; y: Mat } | null;
1351
- ```
1352
-
1353
- ```javascript
1354
- const mag = cv.magnitude({ x: dx, y: dy });
1355
- const polar = cv.cartToPolar({ x: dx, y: dy, angleInDegrees: true });
1356
- cv.release(polar.magnitude); cv.release(polar.angle);
1357
- ```
1358
-
1359
- ### PSNR / scaleAdd / sum
1360
-
1361
- ```typescript
1362
- function PSNR(src1: Mat, options: { src2: Mat; R?: number }): number;
1363
- function scaleAdd(src1: Mat, options: { alpha: number; src2: Mat }): Mat | null;
1364
- function sum(mat: Mat): number[] | null;
1365
- ```
1366
-
1367
- ```javascript
1368
- const psnr = cv.PSNR(a, { src2: b });
1369
- const s = cv.sum(src); // 各通道和
1370
- ```
1371
-
1372
- ---
1373
-
1374
1219
  ## Imgproc 几何 / 滤波扩展
1375
1220
 
1376
- ### stackBlur / sepFilter2D / remap / warpPolar
1377
-
1378
- ```typescript
1379
- function stackBlur(mat: Mat, options: { ksize: number | [number, number] | Size }): Mat | null;
1380
- function sepFilter2D(mat: Mat, options: { kernelX: Mat; kernelY: Mat; ddepth?: number; delta?: number }): Mat | null;
1381
- function remap(mat: Mat, options: { map1: Mat; map2: Mat; interpolation?: number; borderMode?: number }): Mat | null;
1382
- function warpPolar(mat: Mat, options: {
1383
- width?: number; height?: number;
1384
- center: Point; maxRadius: number; flags?: number;
1385
- }): Mat | null;
1386
- ```
1387
-
1388
- ```javascript
1389
- const blur = cv.stackBlur(src, { ksize: 7 });
1390
- const polar = cv.warpPolar(src, {
1391
- width: 300, height: 300, center: { x: 160, y: 160 }, maxRadius: 150,
1392
- flags: cv.INTER_LINEAR | cv.WARP_POLAR_LINEAR,
1393
- });
1394
- ```
1395
-
1396
- ### invertAffineTransform / getRectSubPix / phaseCorrelate
1397
-
1398
- ```typescript
1399
- function invertAffineTransform(M: Mat): Mat | null;
1400
- function getRectSubPix(mat: Mat, options: {
1401
- patchSize: Size | [number, number]; center: Point;
1402
- }): Mat | null;
1403
- function phaseCorrelate(src1: Mat, options: { src2: Mat; window?: Mat }): {
1404
- x: number; y: number; response?: number;
1405
- } | null;
1406
- ```
1407
-
1408
- ```javascript
1409
- const inv = cv.invertAffineTransform(M);
1410
- const shift = cv.phaseCorrelate(a, { src2: b });
1411
- ```
1412
-
1413
- ### HoughLines / LSD
1414
-
1415
- ```typescript
1416
- function HoughLines(mat: Mat, options?: {
1417
- rho?: number; theta?: number; threshold?: number; srn?: number; stn?: number;
1418
- }): Array<{ rho: number; theta: number }> | null;
1419
- function LSD(mat: Mat, options?: object): Array<{ x1: number; y1: number; x2: number; y2: number }> | null;
1420
- ```
1421
-
1422
- ```javascript
1423
- const lines = cv.HoughLines(edges, { threshold: 80 });
1424
- const segs = cv.LSD(gray);
1425
- ```
1426
-
1427
- ### cornerHarris / cornerSubPix / cornerMinEigenVal / preCornerDetect / spatialGradient
1428
-
1429
- ```typescript
1430
- function cornerHarris(mat: Mat, options?: { blockSize?: number; ksize?: number; k?: number }): Mat | null;
1431
- function cornerSubPix(mat: Mat, options: {
1432
- corners: Point[]; winSize?: Size | [number, number]; zeroZone?: Size | [number, number];
1433
- criteria?: { type?: number; maxCount?: number; epsilon?: number };
1434
- }): Point[] | null;
1435
- function cornerMinEigenVal(mat: Mat, options?: { blockSize?: number; ksize?: number }): Mat | null;
1436
- function preCornerDetect(mat: Mat, options?: { ksize?: number }): Mat | null;
1437
- function spatialGradient(mat: Mat, options?: { ksize?: number }): { dx: Mat; dy: Mat } | null;
1438
- ```
1439
-
1440
- ```javascript
1441
- const corners = cv.goodFeaturesToTrack(gray, { maxCorners: 50, qualityLevel: 0.01, minDistance: 10 });
1442
- const refined = cv.cornerSubPix(gray, { corners, winSize: [5, 5] });
1443
- const g = cv.spatialGradient(gray);
1444
- cv.release(g.dx); cv.release(g.dy);
1445
- ```
1446
-
1447
- ### calcBackProject / applyColorMap / HuMoments / pyrMeanShiftFiltering / integral
1448
-
1449
- ```typescript
1450
- function calcBackProject(mat: Mat, options: {
1451
- hist: Mat; channels?: number[]; ranges?: number[]; scale?: number;
1452
- }): Mat | null;
1453
- function applyColorMap(mat: Mat, options: { colormap: number }): Mat | null;
1454
- function HuMoments(contourOrMoments: Point[] | object | Mat): number[] | null;
1455
- function pyrMeanShiftFiltering(mat: Mat, options?: { sp?: number; sr?: number; maxLevel?: number }): Mat | null;
1456
- function integral(mat: Mat): Mat | null;
1457
- ```
1458
-
1459
- ```javascript
1460
- const back = cv.calcBackProject(hsv, { hist, channels: [0], ranges: [0, 180] });
1461
- const heat = cv.applyColorMap(gray, { colormap: cv.COLORMAP_JET });
1462
- const hu = cv.HuMoments(contour); // length 7
1463
- ```
1464
-
1465
- ### drawMarker / arrowedLine / fillConvexPoly(就地)
1466
-
1467
- ```typescript
1468
- function drawMarker(mat: Mat, options: {
1469
- position: Point; color?: number[] | string;
1470
- markerType?: number; markerSize?: number; thickness?: number;
1471
- }): Mat | null;
1472
- function arrowedLine(mat: Mat, options: {
1473
- pt1: Point; pt2: Point; color?: number[] | string; thickness?: number; tipLength?: number;
1474
- }): Mat | null;
1475
- function fillConvexPoly(mat: Mat, options: { pts: Point[]; color?: number[] | string }): Mat | null;
1476
- ```
1477
-
1478
- 就地修改输入 Mat,返回同一句柄。
1479
-
1480
- ```javascript
1481
- cv.drawMarker(src, { position: { x: 100, y: 80 }, markerType: cv.MARKER_CROSS });
1482
- cv.arrowedLine(src, { pt1: { x: 10, y: 10 }, pt2: { x: 200, y: 120 }, color: [0, 0, 255] });
1483
- cv.fillConvexPoly(src, { pts: [{ x: 10, y: 10 }, { x: 100, y: 20 }, { x: 50, y: 80 }], color: [0, 255, 0] });
1484
- ```
1485
-
1486
- ### connectedComponents / convexityDefects / isContourConvex / fitLine
1221
+ ### connectedComponents / convexityDefects / isContourConvex / fitLine / fitEllipse
1487
1222
 
1488
1223
  ```typescript
1489
1224
  function connectedComponents(mat: Mat, options?: { connectivity?: number }): { count: number; labels: Mat } | null;
@@ -1493,46 +1228,29 @@ function isContourConvex(contour: Point[]): boolean;
1493
1228
  function fitLine(points: Point[], options?: {
1494
1229
  distType?: number; param?: number; reps?: number; aeps?: number;
1495
1230
  }): [number, number, number, number] | null;
1231
+ function fitEllipse(points: Point[]): RotatedRect | null;
1496
1232
  ```
1497
1233
 
1498
- `hull` 可为索引数组,或与 `convexHull` 返回格式相同的 Point[]
1234
+ `hull` 可为索引数组,或与 `convexHull` 返回格式相同的 Point[]。`fitEllipse` 至少需要 5 个点。
1499
1235
 
1500
1236
  ```javascript
1501
1237
  const cc = cv.connectedComponents(bin, { connectivity: 8 });
1502
1238
  const hull = cv.convexHull(contour);
1503
1239
  const defects = cv.convexityDefects(contour, { hull });
1504
1240
  const line = cv.fitLine(points); // [vx, vy, x0, y0]
1241
+ const ell = cv.fitEllipse(contour); // { center, size, angle }
1505
1242
  cv.release(cc.labels);
1506
1243
  ```
1507
1244
 
1508
- ### watershed / grabCut(就地)
1245
+ ### getRectSubPix / phaseCorrelate / estimateAffine2D / estimateAffinePartial2D
1509
1246
 
1510
1247
  ```typescript
1511
- /** 就地改写 markers,返回 markers 句柄 */
1512
- function watershed(mat: Mat, options: { markers: Mat }): Mat | null;
1513
- /** 就地改写 mask,返回 mask 句柄 */
1514
- function grabCut(mat: Mat, options: {
1515
- mask: Mat; rect?: Rect; iterCount?: number; mode?: number;
1248
+ function getRectSubPix(mat: Mat, options: {
1249
+ patchSize: Size | [number, number]; center: Point;
1516
1250
  }): Mat | null;
1517
- ```
1518
-
1519
- ```javascript
1520
- cv.watershed(src, { markers }); // 就地改 markers
1521
- cv.grabCut(src, {
1522
- mask,
1523
- rect: { x: 50, y: 50, width: 200, height: 200 },
1524
- iterCount: 5,
1525
- mode: cv.GC_INIT_WITH_RECT,
1526
- }); // 就地改 mask
1527
- ```
1528
-
1529
- ---
1530
-
1531
- ## Calib3d / Features / Objdetect
1532
-
1533
- ### estimateAffine2D / estimateAffinePartial2D
1534
-
1535
- ```typescript
1251
+ function phaseCorrelate(src1: Mat, options: { src2: Mat; window?: Mat }): {
1252
+ x: number; y: number; response?: number;
1253
+ } | null;
1536
1254
  function estimateAffine2D(options: {
1537
1255
  srcPoints: Point[]; dstPoints: Point[]; method?: number; ransacReprojThreshold?: number;
1538
1256
  }): Mat | null;
@@ -1541,57 +1259,34 @@ function estimateAffinePartial2D(options: {
1541
1259
  }): Mat | null;
1542
1260
  ```
1543
1261
 
1544
- ```javascript
1545
- const M = cv.estimateAffine2D({
1546
- srcPoints, dstPoints, method: cv.RANSAC, ransacReprojThreshold: 3,
1547
- });
1548
- ```
1549
-
1550
- ### KAZE / MSER / SimpleBlobDetector / AGAST
1551
-
1552
- ```typescript
1553
- function KAZE(mat: Mat, options?: { mask?: Mat }): { keypoints: KeyPoint[]; descriptors: Mat | null } | null;
1554
- function MSER(mat: Mat, options?: { mask?: Mat }): { keypoints: KeyPoint[] } | null;
1555
- function SimpleBlobDetector(mat: Mat, options?: { mask?: Mat }): { keypoints: KeyPoint[] } | null;
1556
- function AGAST(mat: Mat, options?: {
1557
- threshold?: number; nonmaxSuppression?: boolean; mask?: Mat;
1558
- }): { keypoints: KeyPoint[] } | null;
1559
- ```
1262
+ `phaseCorrelate` 估计两帧位移(滚动/拖拽);`estimateAffinePartial2D` 为相似变换(平移+旋转+等比缩放)。
1560
1263
 
1561
1264
  ```javascript
1562
- const kaze = cv.KAZE(gray);
1563
- const mser = cv.MSER(gray);
1564
- const blobs = cv.SimpleBlobDetector(gray);
1565
- const agast = cv.AGAST(gray, { threshold: 10 });
1566
- if (kaze.descriptors) cv.release(kaze.descriptors);
1265
+ const shift = cv.phaseCorrelate(prevGray, { src2: gray });
1266
+ const M = cv.estimateAffinePartial2D({ srcPoints, dstPoints, method: cv.RANSAC });
1567
1267
  ```
1568
1268
 
1569
- ### encodeQRCode / CascadeClassifier / HOGDetect
1269
+ ### drawMarker / arrowedLine / fillConvexPoly(就地)
1570
1270
 
1571
1271
  ```typescript
1572
- function encodeQRCode(text: string): Mat | null;
1573
- function CascadeClassifier(mat: Mat, options: {
1574
- cascadePath?: string; path?: string;
1575
- scaleFactor?: number; minNeighbors?: number; minSize?: Size | [number, number];
1576
- }): Rect[] | null;
1577
- function HOGDetect(mat: Mat, options?: object): Rect[] | null;
1272
+ function drawMarker(mat: Mat, options: {
1273
+ position: Point; color?: number[] | string;
1274
+ markerType?: number; markerSize?: number; thickness?: number;
1275
+ }): Mat | null;
1276
+ function arrowedLine(mat: Mat, options: {
1277
+ pt1: Point; pt2: Point; color?: number[] | string; thickness?: number; tipLength?: number;
1278
+ }): Mat | null;
1279
+ function fillConvexPoly(mat: Mat, options: { pts: Point[]; color?: number[] | string }): Mat | null;
1578
1280
  ```
1579
1281
 
1580
- `CascadeClassifier` 需自行提供 xml 级联路径。
1282
+ 就地修改输入 Mat,返回同一句柄。适合调试标注匹配点、滑动方向等。
1581
1283
 
1582
1284
  ```javascript
1583
- const qr = cv.encodeQRCode('https://example.com');
1584
- const faces = cv.CascadeClassifier(gray, {
1585
- cascadePath: '/path/to/haarcascade_frontalface_alt.xml',
1586
- scaleFactor: 1.1,
1587
- minNeighbors: 3,
1588
- });
1589
- const people = cv.HOGDetect(src);
1590
- cv.release(qr);
1285
+ cv.drawMarker(src, { position: { x: 100, y: 80 }, markerType: cv.MARKER_CROSS });
1286
+ cv.arrowedLine(src, { pt1: { x: 10, y: 10 }, pt2: { x: 120, y: 80 }, color: [0, 255, 0] });
1287
+ cv.fillConvexPoly(src, { pts: [{ x: 20, y: 20 }, { x: 70, y: 20 }, { x: 45, y: 60 }], color: [128, 0, 128] });
1591
1288
  ```
1592
1289
 
1593
- ---
1594
-
1595
1290
  ## 常用常量
1596
1291
 
1597
1292
  挂在 `cv` 上,例如:
@@ -1614,13 +1309,9 @@ cv.release(qr);
1614
1309
  - 形状匹配:`CONTOURS_MATCH_I1` / `I2` / `I3`
1615
1310
  - 连通域统计列:`CC_STAT_LEFT` / `TOP` / `WIDTH` / `HEIGHT` / `AREA`
1616
1311
  - 读标志:`IMREAD_COLOR`、`IMREAD_GRAYSCALE`、`IMREAD_UNCHANGED`
1617
- - 修复 / 风格:`INPAINT_NS`、`INPAINT_TELEA`、`RECURS_FILTER`、`NORMCONV_FILTER`
1618
- - 无缝克隆:`NORMAL_CLONE`、`MIXED_CLONE`、`MONOCHROME_TRANSFER`
1312
+ - 修复:`INPAINT_NS`、`INPAINT_TELEA`
1619
1313
  - 比较:`CMP_EQ` / `CMP_GT` / `CMP_GE` / `CMP_LT` / `CMP_LE` / `CMP_NE`
1620
- - 伪彩色:`COLORMAP_JET`、`COLORMAP_VIRIDIS`、`COLORMAP_TURBO`、`COLORMAP_HOT`、…
1621
- - 极坐标:`WARP_POLAR_LINEAR`、`WARP_POLAR_LOG`
1622
- - 标记:`MARKER_CROSS`、`MARKER_STAR`、`MARKER_DIAMOND`、…
1623
- - GrabCut:`GC_BGD` / `GC_FGD` / `GC_PR_BGD` / `GC_PR_FGD`、`GC_INIT_WITH_RECT` / `GC_INIT_WITH_MASK` / `GC_EVAL`
1314
+ - 标记:`MARKER_CROSS`、`MARKER_STAR`、`MARKER_DIAMOND`、`MARKER_SQUARE`、…
1624
1315
 
1625
1316
  完整列表见类型提示 `types/opencv.d.ts`。
1626
1317
 
@@ -1628,11 +1319,10 @@ cv.release(qr);
1628
1319
 
1629
1320
  ## 限制与建议
1630
1321
 
1631
- 1. 同时存活的 Mat 句柄有上限(约 32),用完请 `release`,或阶段结束 `releaseAll`;多返回值 API(如 `pencilSketch`、`decolor`、`cartToPolar`)需分别释放。
1322
+ 1. 同时存活的 Mat 句柄有上限(约 32),用完请 `release`,或阶段结束 `releaseAll`。
1632
1323
  2. 路径 / msbundle / imageId / `screen` 只走 `imread`;其它 API 只接受 Mat 句柄。
1633
1324
  3. SIFT 描述子优先 `flannKnnMatch`;ORB 用 `BFMatcher` + `NORM_HAMMING`。
1634
1325
  4. 特征匹配前建议转灰度;大图可先 `pyrDown` / `resize`。
1635
1326
  5. `findHomography` 至少需要 4 对对应点,不足会返回 `null`。
1636
- 6. 绘制类、`floodFill`、`insertChannel`、`watershed`、`grabCut` 会改原图或指定目标句柄;需保留原图请先 `clone`。
1637
- 7. 与 `image` 互通时,两边句柄各自 `release`。
1638
- 8. `CascadeClassifier` 需要设备上可访问的级联 xml 路径。
1327
+ 6. 绘制类、`floodFill` 会改原图或指定目标句柄;需保留原图请先 `clone`。
1328
+ 7. 与 `image` 互通时,两边句柄各自 `release`。
@@ -85,7 +85,7 @@ function 识别绝对坐标(
85
85
  | `输入源` | 字符串 | 是 | | 输入源,支持 `"screen"`、图片文件路径、URL 或 imageId |
86
86
  | `左`, `上` | 数字 | 否 | 0 | 裁剪区域左上角坐标 |
87
87
  | `右`, `下` | 数字 | 否 | 0 | 裁剪区域右下角坐标;`右` 或 `下` 为 0 时分别使用图像宽度或高度 |
88
- | `置信度阈值` | 数字 | 否 | 0.8 | 相似度下限;`<=0` 时按 `0.8`;低于阈值的字符会被过滤;`>0.95` 时要求模板与图像像素完全一致 |
88
+ | `置信度阈值` | 数字 | 否 | 0.8 | 相似度下限;`<=0` 时按 `0.8`;低于阈值的字符会被过滤;`>0.95` 时要求像素完全一致 |
89
89
 
90
90
  **返回值:**`数组<OCR字符>`。按行从上到下、同一行从左到右排列。颜色按字库各字自带的前景色/偏色匹配,无需额外传色。
91
91
 
@@ -121,7 +121,7 @@ function 查找文本绝对坐标(
121
121
  | `左`, `上` | 数字 | 否 | 0 | 裁剪区域左上角坐标 |
122
122
  | `右`, `下` | 数字 | 否 | 0 | 裁剪区域右下角坐标;`右` 或 `下` 为 0 时分别使用图像宽度或高度 |
123
123
  | `置信度阈值` | 数字 | 否 | 0.8 | 单字符识别相似度下限;语义同 `识别绝对坐标` |
124
- | `完整匹配` | 布尔值 | 否 | false | `false` 为行内子串包含匹配;`true` 要求某一整行文本完全等于目标字符串 |
124
+ | `完整匹配` | 布尔值 | 否 | false | `false` 为行内子串包含匹配;`true` 要求某一整行文本完全等于目标字符串 |
125
125
 
126
126
  **返回值:**命中结果数组。每个目标最多返回一次命中(最先匹配到的行);每项的 `text` 是目标字符串。
127
127
 
@@ -90,6 +90,13 @@ interface $节点信息 {
90
90
  点击中心(): 布尔值; // 点击中心
91
91
  点击随机范围(): 布尔值; // 随机点击
92
92
  hittable(): 布尔值; // 节点是否可接收事件,用于判断节点是否在屏幕可见
93
+ tap(): 布尔值; // 无障碍点击
94
+ doubleTap(): 布尔值; // 无障碍双击
95
+ press(duration: 数字): 布尔值; // 无障碍长按,duration 单位毫秒
96
+ scrollToVisible(): 布尔值; // 将节点滚动到可见区域
97
+ clearInput(): 布尔值; // 清空输入
98
+ input(text: 字符串): 布尔值; // 追加输入(聚焦后输入)
99
+ setValue(text: 字符串): 布尔值; // 设置节点的值(见下方支持控件)
93
100
  parent(): $节点信息 | null; // 获取父节点
94
101
  child(index: 数字): $节点信息 | null; // 获取子节点
95
102
  allChildren(): $节点信息[]; // 获取所有子节点
@@ -274,16 +281,82 @@ const xpathElements = 选择器.xpath("//*[@title='快点JS']").获取所有节
274
281
  #### 点击操作
275
282
 
276
283
  ```typescript
277
- // 点击节点中心
284
+ // 点击节点中心(坐标点击)
278
285
  node.点击中心(): 布尔值;
279
286
 
280
- // 随机点击节点区域
287
+ // 随机点击节点区域(坐标点击)
281
288
  node.点击随机范围(): 布尔值;
282
289
 
283
290
  // 节点是否可接收事件(用于判断是否显示在屏幕上)
284
291
  node.hittable(): 布尔值;
292
+
293
+ // 无障碍点击
294
+ node.tap(): 布尔值;
295
+
296
+ // 无障碍双击
297
+ node.doubleTap(): 布尔值;
298
+
299
+ // 无障碍长按,duration 单位毫秒
300
+ node.press(duration: 数字): 布尔值;
301
+
302
+ // 将节点滚动到可见区域
303
+ node.scrollToVisible(): 布尔值;
304
+
305
+ // 清空输入
306
+ node.clearInput(): 布尔值;
307
+
308
+ // 追加输入
309
+ node.input(text: 字符串): 布尔值;
310
+
311
+ // 设置节点的值
312
+ node.setValue(text: 字符串): 布尔值;
285
313
  ```
286
314
 
315
+ #### tap / doubleTap / press - 无障碍手势
316
+
317
+ 通过无障碍 API 直接操作节点,不依赖坐标点击。
318
+
319
+ | 方法 | 参数 | 说明 |
320
+ | --- | --- | --- |
321
+ | `tap()` | 无 | 无障碍单击 |
322
+ | `doubleTap()` | 无 | 无障碍双击 |
323
+ | `press(duration)` | `duration`:长按持续时间,单位毫秒 | 无障碍长按 |
324
+
325
+ **示例:**
326
+
327
+ ```typescript
328
+ const 选择器 = $创建节点选择器();
329
+
330
+ const btn = 选择器.label("确定").type("XCUIElementTypeButton").获取一个节点(3000);
331
+ if (btn) {
332
+ // 无障碍单击
333
+ btn.tap();
334
+
335
+ // 无障碍双击
336
+ btn.doubleTap();
337
+
338
+ // 无障碍长按 800ms
339
+ btn.press(800);
340
+ }
341
+
342
+ // 释放选择器内存
343
+ 选择器.释放内存();
344
+ ```
345
+
346
+ #### setValue - 设置节点的值
347
+
348
+ 按控件类型自动处理,常用支持如下:
349
+
350
+ | 控件类型 | 传参示例 | 说明 |
351
+ | --- | --- | --- |
352
+ | `XCUIElementTypePickerWheel` | `"1小时"` | 滚轮选到对应选项文案 |
353
+ | `XCUIElementTypeTextField` | `"hello"` | 先聚焦再输入文本 |
354
+ | `XCUIElementTypeSecureTextField` | `"123456"` | 同上 |
355
+ | `XCUIElementTypeTextView` | `"多行内容"` | 同上 |
356
+ | `XCUIElementTypeSearchField` | `"关键词"` | 同上 |
357
+
358
+ 文本类控件不会先清空已有内容;需要清空时先调用 `clearInput()`。
359
+
287
360
  **示例:**
288
361
 
289
362
  ```typescript
@@ -298,12 +371,25 @@ if (confirmBtn) {
298
371
  const isHittable = confirmBtn.hittable();
299
372
  $打印信息日志(`是否可接收事件: ${isHittable}`);
300
373
 
301
- const clicked = confirmBtn.点击中心();
374
+ confirmBtn.scrollToVisible();
375
+ // 优先使用无障碍点击;也可用 点击中心 / 点击随机范围 做坐标点击
376
+ const clicked = confirmBtn.tap();
302
377
  if (clicked) {
303
378
  $打印信息日志("成功点击确定按钮");
304
379
  }
305
380
  }
306
381
 
382
+ const field = 选择器.type("XCUIElementTypeTextField").获取一个节点(3000);
383
+ if (field) {
384
+ field.clearInput();
385
+ field.setValue("hello");
386
+ }
387
+
388
+ const wheel = 选择器.type("XCUIElementTypePickerWheel").获取一个节点(3000);
389
+ if (wheel) {
390
+ wheel.setValue("1小时");
391
+ }
392
+
307
393
  // 随机点击避免检测
308
394
  const randomClick = confirmBtn?.点击随机范围();
309
395
  if (randomClick) {
@@ -81,7 +81,7 @@ def recognizeAbs(
81
81
  | `input` | str | 是 | | 输入源,支持 `"screen"`、图片文件路径、URL 或 imageId |
82
82
  | `x`, `y` | int | 否 | 0 | 裁剪区域左上角坐标 |
83
83
  | `ex`, `ey` | int | 否 | 0 | 裁剪区域右下角坐标;`ex` 或 `ey` 为 0 时分别使用图像宽度或高度 |
84
- | `confidenceThreshold` | float | 否 | 0.8 | 相似度下限;`<=0` 时按 `0.8`;低于阈值的字符会被过滤;`>0.95` 时要求模板与图像像素完全一致 |
84
+ | `confidenceThreshold` | float | 否 | 0.8 | 相似度下限;`<=0` 时按 `0.8`;低于阈值的字符会被过滤;`>0.95` 时要求像素完全一致 |
85
85
 
86
86
  **返回值:**`List[OCRChar]`。按行从上到下、同一行从左到右排列。颜色按字库各字自带的前景色/偏色匹配,无需额外传色。
87
87
 
@@ -117,7 +117,7 @@ def findTextAbs(
117
117
  | `x`, `y` | int | 否 | 0 | 裁剪区域左上角坐标 |
118
118
  | `ex`, `ey` | int | 否 | 0 | 裁剪区域右下角坐标;`ex` 或 `ey` 为 0 时分别使用图像宽度或高度 |
119
119
  | `confidenceThreshold` | float | 否 | 0.8 | 单字符识别相似度下限;语义同 `recognizeAbs` |
120
- | `exactMatch` | bool | 否 | false | `false` 为行内子串包含匹配;`true` 要求某一整行文本完全等于目标字符串 |
120
+ | `exactMatch` | bool | 否 | false | `false` 为行内子串包含匹配;`true` 要求某一整行文本完全等于目标字符串 |
121
121
 
122
122
  **返回值:**命中结果数组。每个目标最多返回一次命中(最先匹配到的行);每项的 `text` 是目标字符串。
123
123
 
@@ -37,6 +37,13 @@ class NodeInfo:
37
37
  def clickCenter(self) -> bool: ...
38
38
  def clickRandom(self) -> bool: ...
39
39
  def hittable(self) -> bool: ...
40
+ def tap(self) -> bool: ...
41
+ def doubleTap(self) -> bool: ...
42
+ def press(self, duration: int = 500) -> bool: ...
43
+ def scrollToVisible(self) -> bool: ...
44
+ def clearInput(self) -> bool: ...
45
+ def input(self, text: str) -> bool: ...
46
+ def setValue(self, text: str) -> bool: ...
40
47
  def parent(self) -> Optional["NodeInfo"]: ...
41
48
  def child(self, index: int) -> Optional["NodeInfo"]: ...
42
49
  def allChildren(self) -> List["NodeInfo"]: ...
@@ -253,16 +260,80 @@ xpath_elements = selector.xpath("//*[@title='快点JS']").getNodeInfo(3000)
253
260
  ### 点击操作
254
261
 
255
262
  ```python
256
- # 点击节点中心
263
+ # 点击节点中心(坐标点击)
257
264
  def clickCenter() -> bool
258
265
 
259
- # 随机点击节点区域
266
+ # 随机点击节点区域(坐标点击)
260
267
  def clickRandom() -> bool
261
268
 
262
269
  # 节点是否可接收事件(用于判断是否显示在屏幕上)
263
270
  def hittable() -> bool
271
+
272
+ # 无障碍点击
273
+ def tap() -> bool
274
+
275
+ # 无障碍双击
276
+ def doubleTap() -> bool
277
+
278
+ # 无障碍长按,duration 单位毫秒,默认 500
279
+ def press(duration: int = 500) -> bool
280
+
281
+ # 将节点滚动到可见区域
282
+ def scrollToVisible() -> bool
283
+
284
+ # 清空输入
285
+ def clearInput() -> bool
286
+
287
+ # 追加输入
288
+ def input(text: str) -> bool
289
+
290
+ # 设置节点的值
291
+ def setValue(text: str) -> bool
264
292
  ```
265
293
 
294
+ #### tap / doubleTap / press - 无障碍手势
295
+
296
+ 通过无障碍 API 直接操作节点,不依赖坐标点击。
297
+
298
+ | 方法 | 参数 | 说明 |
299
+ | --- | --- | --- |
300
+ | `tap()` | 无 | 无障碍单击 |
301
+ | `doubleTap()` | 无 | 无障碍双击 |
302
+ | `press(duration=500)` | `duration`:长按持续时间,单位毫秒,默认 500 | 无障碍长按 |
303
+
304
+ **示例:**
305
+
306
+ ```python
307
+ from kuaijs import node
308
+
309
+ selector = node.createNodeSelector()
310
+
311
+ btn = selector.label("确定").type("XCUIElementTypeButton").getOneNodeInfo(3000)
312
+ if btn:
313
+ # 无障碍单击
314
+ btn.tap()
315
+
316
+ # 无障碍双击
317
+ btn.doubleTap()
318
+
319
+ # 无障碍长按 800ms
320
+ btn.press(800)
321
+ ```
322
+
323
+ #### setValue - 设置节点的值
324
+
325
+ 按控件类型自动处理,常用支持如下:
326
+
327
+ | 控件类型 | 传参示例 | 说明 |
328
+ | --- | --- | --- |
329
+ | `XCUIElementTypePickerWheel` | `"1小时"` | 滚轮选到对应选项文案 |
330
+ | `XCUIElementTypeTextField` | `"hello"` | 先聚焦再输入文本 |
331
+ | `XCUIElementTypeSecureTextField` | `"123456"` | 同上 |
332
+ | `XCUIElementTypeTextView` | `"多行内容"` | 同上 |
333
+ | `XCUIElementTypeSearchField` | `"关键词"` | 同上 |
334
+
335
+ 文本类控件不会先清空已有内容;需要清空时先调用 `clearInput()`。
336
+
266
337
  **示例:**
267
338
 
268
339
  ```python
@@ -276,12 +347,23 @@ confirm_btn = selector.label("确定").type("XCUIElementTypeButton").getOneNodeI
276
347
  if confirm_btn:
277
348
  print(f"是否可接收事件: {confirm_btn.hittable()}")
278
349
 
279
- clicked = confirm_btn.clickCenter()
350
+ confirm_btn.scrollToVisible()
351
+ # 优先使用无障碍点击;也可用 clickCenter / clickRandom 做坐标点击
352
+ clicked = confirm_btn.tap()
280
353
  if clicked:
281
354
  print("成功点击确定按钮")
282
355
 
283
- # 随机点击避免检测
356
+ # 随机坐标点击避免检测
284
357
  confirm_btn.clickRandom()
358
+
359
+ field = selector.type("XCUIElementTypeTextField").getOneNodeInfo(3000)
360
+ if field:
361
+ field.clearInput()
362
+ field.setValue("hello")
363
+
364
+ wheel = selector.type("XCUIElementTypePickerWheel").getOneNodeInfo(3000)
365
+ if wheel:
366
+ wheel.setValue("1小时")
285
367
  ```
286
368
 
287
369
  ### 节点导航
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ms-vite-plugin",
3
- "version": "1.4.40",
3
+ "version": "1.4.41",
4
4
  "type": "commonjs",
5
5
  "license": "MIT",
6
6
  "publishConfig": {