feat(步骤): 选择器支持 {变量} + 新增「读取文字」+ 条件判断可选「变量」;fix(发布): 剪辑页校验等 45 秒、切号前先回主界面

用户要求:"把你每一步操作都放出来让我自己写,你都封在一起我也不知道切号/认号的流程"
→ 把两个打包步骤的内部动作,拆成**画布上拖得出来的普通步骤**(能拆是因为补了下面两个零件)。

① 选择器值里的**变量**(tasks/generic/task.py `_vars_sub`)
   `{本次要发的名字}`(循环块设的)/ `{本机账号名字}`(读名字读到的),中文别名与变量名都认。
   用在 click / long_click / wait_el / swipe_until / expect_el / read_text / read_account_name /
   switch_account 的选择器值,以及 if_el 的选择器与手填候选值。
   · 没写花括号的步骤一个字不改(老任务零影响)
   · 变量为空 → **换成空串 + 记警告**:宁可"选择器变空、找不到元素"明着报,
     也不留 `{…}` 字面量去点到歪的地方
   · 不做通用表达式(只认表里那几个名字)

② 「点击元素」的选择器类型下拉补上 **textContains**(文本包含)——
   以前只能用打包步骤做到的"按名字点列表那一行",现在自己就能拼:
   `textContains` + 值 `{本次要发的名字}`

③ 新步骤 **read_text「读取文字」**:把元素文字读出来存进指定变量
   (`var`=account_name 时顺手把昵称控件记给切号/推送核对复用;`clean`=按账号名清洗/只去空白;
   `on_missing`=skip 默认只记一句 / fail 算失败)。之前**没有**"读取元素文字"这种步骤。

④ 「条件判断」的候选值来源加 **「变量」**(`cmp_source=var` + `cmp_var`)——
   不然 read_text 读出来的东西没处用。空变量 = 候选为空 = 不命中(同"读不到名字"的规矩)。

⑤ fix(发布) 实测踩出来的两处(A06,截图/日志为证):
   · **剪辑页校验 12 → 45 秒**:点完「下一步」抖音在「正在导入」(一条 25 秒的视频要导 25 秒+),
     导入期间工具栏不渲染,10~12 秒必然判"没到剪辑页"→ 这一条白发
   · **切号前先回主界面**(`_goto_me`):点不到「我」页签 = 不在主界面(剪辑页/发布页),
     那种页面没有底部页签 → **强杀 + 冷启动**抖音再来。实测卡在剪辑页时,本轮剩下的号全部报废
   · **切号后回读为空时点一次「我」再读**:换号会整体重启抖音、起来常落在首页(首页没有昵称控件),
     读多少次都是空 —— 等完 wait_after、读两遍仍空才补这一下导航,不打扰它的重启过程

前端:编辑器调色板/字段面板认 read_text(含变量说明)、选择器类型加 textContains、
      placeholder 提示 `{本次要发的名字}`、候选值来源加「变量」+ 变量名输入、
      「发布计划」页就地编辑字段表同步(release.js)。
文档:TASK_DEV §3 步骤表 28→29 种 + §3.3「选择器里的变量」+ §3.4「自己拼切号+认号」;
      API.md 骨架步数修正(18 → 4 步 / 含嵌套 23 步)。
测试:新增 scripts/tests/test_read_text.py(变量代入、空变量不许瞎点、读取文字存变量/清洗、
      条件判断取变量、草稿校验),全套 8 个用例通过。
This commit is contained in:
2026-10-12 13:31:02 +08:00
parent 10d2eb8334
commit 68c16a5fde
9 changed files with 485 additions and 36 deletions
+14 -3
View File
@@ -708,7 +708,8 @@ function rpNewTargetChanged() {
// 保存口径:整块 steps 交给 `PUT /api/video_plan/tasks/<id>`,后端深合并进 params
// (不能整块覆盖 params —— 去重有效期、抢占那些通用参数会被抹掉)。
var _rpEdit = null; // 正在编辑的任务(steps 是深拷贝,取消就丢弃)
var _RP_SEL_TYPES = ['xpath', 'description', 'text', 'resourceId', 'descriptionContains', 'className'];
var _RP_SEL_TYPES = ['xpath', 'description', 'text', 'textContains', 'resourceId',
'descriptionContains', 'className'];
// 每种步骤给哪几个字段(其它参数原样保留,不动它们)
var _RP_FIELDS = {
@@ -740,6 +741,13 @@ var _RP_FIELDS = {
['timeout', '最多等(秒)', 'num'],
['on_missing', '等不到时', 'sel', [['skip_rest', '跳过本轮剩余步骤(推荐)'],
['notify', '只通知、继续'], ['stop', '停本设备']]]],
read_text: [['selector_type', '选择器类型', 'seltype'],
['selector_value', '读哪个元素的文字(可用 {本次要发的名字})', 'selval'],
['var', '存到哪个变量名', 'text'],
['clean', '清洗方式', 'sel', [['account', '按账号名清洗(去空白/按钮字)'],
['strip', '只去首尾空白']]],
['timeout', '超时(秒)', 'num'],
['on_missing', '读不到时', 'sel', [['skip', '记一句继续'], ['fail', '算失败']]]],
read_account_name: [['selector_type', '选择器类型', 'seltype'],
['selector_value', '选择器值(抖音「我」页的昵称)', 'selval'],
['timeout', '超时(秒)', 'num'],
@@ -776,7 +784,9 @@ var _RP_FIELDS = {
['不等于', '不等于'], ['包含', '包含'], ['不包含', '不包含']]],
['cmp_source', '候选值来源', 'sel', [['', '手填'],
['account_target', '本次要发的名字(读名字/循环块给的)'],
['release', '本机当前发布计划(要发的那条)']]],
['release', '本机当前发布计划(要发的那条)'],
['var', '变量(「读取文字」读到的)']]],
['cmp_var', '变量名(上面选"变量"时填)', 'text'],
['cmp_value', '比对的值(一行一个,任一命中即可)', 'area']],
mark_done: [['selector_type', '选择器类型', 'seltype'], ['selector_value', '选择器值', 'selval']],
clipboard: [['text', '要放进剪贴板的文字', 'text']],
@@ -958,8 +968,9 @@ function _rpField(type, p, f, path) {
var pick = (kind === 'selval' && typeof _can === 'function' && _can('devices'))
? '<button class="btn btn-xs btn-pick" style="margin-left:4px" onclick="_stepEditor.openPicker(this)">抓取元素</button>'
: '';
var ph = (kind === 'selval') ? ' placeholder="如 点赞;可用 {本次要发的名字}"' : '';
return '<div class="form-group"' + span + ' style="margin-bottom:0">' + head
+ '<div style="display:flex"><input type="text" class="form-control" ' + attrs + ' value="'
+ '<div style="display:flex"><input type="text" class="form-control"' + ph + ' ' + attrs + ' value="'
+ esc(v == null ? '' : v) + '">' + pick + '</div></div>';
}