feat(任务): 「动作配置」分栏——步骤默认值 + 动作录制器

需求:滑动要能"我自己录制";在自定义动作旁边加一个分栏,能配动作默认值、
能录制操作当动作、能自己建立动作。

**步骤默认值**(core/step_defaults.py,存 app_meta.step_defaults 单键,不建表):
- 覆盖 滑动(方向/时长区间/幅度/抖动/拟人)、点击(等待超时)、长按(时长/超时)、
  等待(区间)、按键、输入文字(模式/候选文案/清空);
- **只影响之后新建的步骤**,已有步骤不动;新建时 `_makeStep` 用出厂值打底 + 默认值覆盖;
- 只存与出厂值不同的字段(以后调出厂默认,没配过的能跟着走);
- 字段白名单 + 范围夹取(SPEC):越界夹边界、非法值丢弃 → 手改 JSON 塞脏值也进不来。

**动作录制器**(static/admin/recorder.js):
- 在设备画面上点/划/按键/输入 → 自动翻译成步骤:
  点→命中元素记「点击元素」(选择器)、没命中退化成「点击坐标」(百分比);
  划→「滑动」方向/幅度/时长全按你实际操作算;停顿≥1s 可选记成「等待」(±15%);
- 两个入口:动作配置页「录制动作」(存成自定义动作,进动作库复用)、
  步骤编辑器滑动那步的「录制手势」(只取手势回填该步骤);
- 画面+元素树走 /api/uiauto/snapshot 一次取齐,**每次操作后自动刷新**,
  所以下一次点击用的是最新的树;执行仍走 /api/screen/{tap,swipe,key,text}(与大屏同一套)。

接口:GET/POST /api/step_defaults(读登录、写需 tasks 权限)。
文档:TASK_DEV §3.1(含翻译规则表与三条实现口径)、API §2.3、DATA_MODEL §5、README。

自测(无头浏览器 + CDP 真模拟拖动):默认值保存→回读→新建滑动步骤确实预填 0.42;
录制器取到画面与元素树;模拟"按住往上划"→ 录出
{direction:up, distance_ratio:0.5, duration_min:0.47, humanize:true}(幅度/时长按实际操作算);
恢复出厂链路 0.6 通过;页面无 JS 报错。
This commit is contained in:
2026-09-20 14:32:35 +08:00
parent 9a44eeba95
commit bd51306a2a
12 changed files with 782 additions and 7 deletions
+26 -2
View File
@@ -7,9 +7,9 @@ from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed, TimeoutError as FuturesTimeout
from flask import (Blueprint, jsonify, request, Response)
from flask_login import login_required
from flask_login import login_required, current_user
from core import device_pool, uiauto_helper
from core import device_pool, uiauto_helper, step_defaults
from core.adb_helper import adb_connect_light
from core.models import db, DeviceGroup, TaskJob, CustomAction
from core.logger import get_logger
@@ -215,6 +215,30 @@ def api_groups_delete(name):
return jsonify({"ok": False, "error": "分组不存在"}), 404
# ================== API:步骤默认值(「动作配置」页) ==================
#
# 存 app_meta.step_defaults 一个键(不建表),校验与合并都在 core/step_defaults.py。
@bp.route("/api/step_defaults")
@login_required
def api_step_defaults_get():
"""生效的默认值 + 出厂值 + 字段规格(前端画表单用)。"""
return jsonify({"ok": True, **step_defaults.as_payload()})
@bp.route("/api/step_defaults", methods=["POST"])
@perm_required(PERM_TASKS)
def api_step_defaults_save():
"""保存默认值(只落与出厂值不同的字段,便于以后出厂值升级能跟着走)。"""
data = request.json or {}
ok, msg = step_defaults.save(data.get("defaults") or data)
if not ok:
return jsonify({"ok": False, "error": msg}), 400
_log.info("更新步骤默认值 by %s: %s", getattr(current_user, "username", ""),
list((data.get("defaults") or data).keys()))
return jsonify({"ok": True, "msg": msg, **step_defaults.as_payload()})
# ================== API:用户管理 CRUD ==================
@bp.route("/api/custom_actions")