From f4641c3e2dfe07cf88d29dd31953b15e6bcb86c6 Mon Sep 17 00:00:00 2001
From: butubb <1422726308@qq.com>
Date: Mon, 12 Oct 2026 13:54:59 +0800
Subject: [PATCH] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E5=99=A8):=20=E9=80=89?=
=?UTF-8?q?=E6=8B=A9=E5=99=A8=E5=80=BC=E6=97=81=E8=BE=B9=E5=8A=A0=E3=80=8C?=
=?UTF-8?q?{=E5=8F=98=E9=87=8F}=E3=80=8D=E6=8C=89=E9=92=AE=20+=20=E3=80=8C?=
=?UTF-8?q?=E7=82=B9=E5=87=BB=E5=85=83=E7=B4=A0=E3=80=8D=E8=A1=A5=E5=8F=98?=
=?UTF-8?q?=E9=87=8F=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
用户反馈:"我看你这个点击元素也没有说点击变量啊" —— 上一版只在输入框 placeholder
里提了一句,等于没说:placeholder 只在空值时才显形,也没个能点的东西。
· 选择器值右边加 **「{变量}」按钮**(在「抓取元素/测试」旁边):点一下弹出
`{本次要发的名字}`(本轮要发的那个号)/ `{本机账号名字}`(读到的本机账号),
选中即插到光标处;通用编辑器和「发布计划」页的就地编辑**两处都有**
· **「点击元素」参数面板加说明块**:讲清"每轮都不一样的东西怎么点"——
类型选 textContains、值填 `{本次要发的名字}`;变量为空会变空串 + 记警告,
**不会拿 `{…}` 字面量去乱点**
· doc/TASK_DEV §3.3 补一句"编辑器里点按钮就行,不用记写法"
---
doc/TASK_DEV.md | 3 +++
static/admin/editor.js | 55 +++++++++++++++++++++++++++++++++++++++--
static/admin/release.js | 6 ++++-
3 files changed, 61 insertions(+), 3 deletions(-)
diff --git a/doc/TASK_DEV.md b/doc/TASK_DEV.md
index 587e214..2a5b5a6 100644
--- a/doc/TASK_DEV.md
+++ b/doc/TASK_DEV.md
@@ -234,6 +234,9 @@ from .generic import task # 触发 @register_task(当前唯一任务类型)
例:账号列表里点自己要发的那个号 —— 选择器类型 `textContains`、值 `{本次要发的名字}`。
+**编辑器里不用记写法**:选择器值右边的 **`{变量}`** 按钮点一下就能插(两个变量各带一句说明);
+「点击元素」的参数面板里也有专门的说明块。
+
**规矩(都是刻意的)**:
- **没写花括号的步骤一个字不改**,老任务零影响。
diff --git a/static/admin/editor.js b/static/admin/editor.js
index 9674ce1..0ab8eab 100644
--- a/static/admin/editor.js
+++ b/static/admin/editor.js
@@ -81,6 +81,8 @@ function _selRowHtml(p, extra, withOcr, withDedup, keys){
var toggle=withOcr?' onchange="_stepEditor._condTypeChanged(this)"':'';
var pick=_can('devices')?'':'';
var test=_can('devices')?'':'';
+ // 变量按钮:选择器值里可以直接写 {本次要发的名字} —— 点它弹出来选,省得记写法
+ var varbtn='';
var isScreen=p[kType]==='screen', isFg=p[kType]==='foreground';
var valLabel=isScreen?'屏幕状态':(isFg?'前台包名':'选择器值');
var valHtml;
@@ -90,7 +92,7 @@ function _selRowHtml(p, extra, withOcr, withDedup, keys){
'';
}else{
valHtml=''+(isFg?'':pick+test);
+ '" placeholder="'+(isFg?'如 com.ss.android.ugc.aweme':'如 点赞;可用 {本次要发的名字} / {本机账号名字}')+'">'+(isFg?'':pick+test+varbtn);
}
var head='
'+
'
';
@@ -379,7 +381,13 @@ var _stepEditor={
h+=_humanizeRowHtml(p,'');
h+='想要"照我划的那一下原样重放",请用操作库里的《录制手势》步骤——那个是纯轨迹回放,不套这里的参数
';
}else if(step.type==='click'){
- h+=_selRowHtml(p,'');
+ h+=_selRowHtml(p,''
+ +'要用"每轮都不一样的东西"点元素?选择器值里可以直接写变量,'
+ +'点值输入框右边的 {变量} 按钮插入:{本次要发的名字}(循环轮到 / 要发的那条计划的名字)、'
+ +'{本机账号名字}(「读取文字」/「读取本机账号名字」读到的)。'
+ +'例:在账号列表里点自己要发的那个号 —— 类型选 textContains(文本包含)、值填 {本次要发的名字}。'
+ +'
变量那一刻还是空的(还没轮到 / 没读到)→ 值会变成空串并记一条警告,'
+ +'这一步就是"找不到元素",不会拿 {…} 字面量去乱点。
');
}else if(step.type==='long_click'){
h+=_selRowHtml(p,'');
@@ -918,6 +926,49 @@ var _stepEditor={
setTimeout(function(){self._renderCanvas();},0);
},
// 条件判断:候选值来源切到"平台给"(本次要发的名字 / 发布计划)时,把"比对的值"藏起来
+ // 在选择器值里插入变量占位符:点「{变量}」→ 弹出两个变量 → 选中即插到光标处
+ insertVar:function(btn){
+ var old=document.getElementById('_varMenu');
+ if(old){old.remove();}
+ var box=btn.closest('.sel-picker')||btn.parentNode;
+ var input=box.querySelector('input[data-key],textarea[data-key]')||box.querySelector('input,textarea');
+ if(!input){return;}
+ var items=[['{本次要发的名字}','本轮要发的那个号(循环块给的)'],
+ ['{本机账号名字}','读到的本机账号(读取文字/读取本机账号名字)']];
+ var menu=document.createElement('div');
+ menu.id='_varMenu';
+ menu.style.cssText='position:fixed;z-index:9999;background:#fff;border:1px solid #d0d5dd;'
+ +'border-radius:6px;box-shadow:0 6px 16px rgba(0,0,0,.16);padding:4px;min-width:260px;font-size:12px';
+ menu.innerHTML=items.map(function(o){
+ return ''
+ +'
'+o[0]+''
+ +'
'+o[1]+'
';}).join('');
+ menu.querySelectorAll('._varItem').forEach(function(el){
+ el.onmouseenter=function(){el.style.background='#f2f4f7';};
+ el.onmouseleave=function(){el.style.background='';};
+ el.onclick=function(e){
+ e.stopPropagation();
+ var t=input.value||'';
+ var a=input.selectionStart==null?t.length:input.selectionStart;
+ var b=input.selectionEnd==null?t.length:input.selectionEnd;
+ input.value=t.slice(0,a)+el.dataset.v+t.slice(b);
+ try{input.setSelectionRange(a+el.dataset.v.length,a+el.dataset.v.length);}catch(_){}
+ input.dispatchEvent(new Event('input',{bubbles:true}));
+ input.dispatchEvent(new Event('change',{bubbles:true}));
+ input.focus();
+ menu.remove();
+ };
+ });
+ document.body.appendChild(menu);
+ var r=btn.getBoundingClientRect();
+ menu.style.left=Math.max(8,Math.min(r.left,window.innerWidth-280))+'px';
+ menu.style.top=(r.bottom+4)+'px';
+ setTimeout(function(){
+ document.addEventListener('click',function h(){
+ var m=document.getElementById('_varMenu'); if(m){m.remove();}
+ document.removeEventListener('click',h);});
+ },0);
+ },
_cmpSourceChanged:function(sel){
// 同 _condTypeChanged:先把 DOM 的值同步回 _steps,否则重渲染会吞掉刚改的东西
this._syncFromDom();
diff --git a/static/admin/release.js b/static/admin/release.js
index 918b8d5..f11ad1c 100644
--- a/static/admin/release.js
+++ b/static/admin/release.js
@@ -968,10 +968,14 @@ function _rpField(type, p, f, path) {
var pick = (kind === 'selval' && typeof _can === 'function' && _can('devices'))
? ''
: '';
+ // 「{变量}」:选择器值里写 {本次要发的名字}(本轮要发的那个号)—— 点它插进去,不用记写法
+ var varbtn = (kind === 'selval' && typeof _stepEditor !== 'undefined' && _stepEditor.insertVar)
+ ? ''
+ : '';
var ph = (kind === 'selval') ? ' placeholder="如 点赞;可用 {本次要发的名字}"' : '';
return '';
+ + esc(v == null ? '' : v) + '">' + pick + varbtn + '';
}
// 抓取元素回填后:把选择器类型下拉也切过去(通用编辑器那套要看 .sc-body,这里结构不同)