feat(编辑器): 选择器值旁边加「{变量}」按钮 + 「点击元素」补变量说明

用户反馈:"我看你这个点击元素也没有说点击变量啊" —— 上一版只在输入框 placeholder
里提了一句,等于没说:placeholder 只在空值时才显形,也没个能点的东西。

· 选择器值右边加 **「{变量}」按钮**(在「抓取元素/测试」旁边):点一下弹出
  `{本次要发的名字}`(本轮要发的那个号)/ `{本机账号名字}`(读到的本机账号),
  选中即插到光标处;通用编辑器和「发布计划」页的就地编辑**两处都有**
· **「点击元素」参数面板加说明块**:讲清"每轮都不一样的东西怎么点"——
  类型选 textContains、值填 `{本次要发的名字}`;变量为空会变空串 + 记警告,
  **不会拿 `{…}` 字面量去乱点**
· doc/TASK_DEV §3.3 补一句"编辑器里点按钮就行,不用记写法"
This commit is contained in:
2026-10-12 13:54:59 +08:00
parent 12ac599a27
commit f4641c3e2d
3 changed files with 61 additions and 3 deletions
+3
View File
@@ -234,6 +234,9 @@ from .generic import task # 触发 @register_task(当前唯一任务类型)
例:账号列表里点自己要发的那个号 —— 选择器类型 `textContains`、值 `{本次要发的名字}`。
**编辑器里不用记写法**:选择器值右边的 **`{变量}`** 按钮点一下就能插(两个变量各带一句说明);
「点击元素」的参数面板里也有专门的说明块。
**规矩(都是刻意的)**:
- **没写花括号的步骤一个字不改**,老任务零影响。
+53 -2
View File
@@ -81,6 +81,8 @@ function _selRowHtml(p, extra, withOcr, withDedup, keys){
var toggle=withOcr?' onchange="_stepEditor._condTypeChanged(this)"':'';
var pick=_can('devices')?'<button class="btn btn-xs btn-pick" onclick="_stepEditor.openPicker(this)">抓取元素</button>':'';
var test=_can('devices')?'<button class="btn btn-xs" onclick="_stepEditor._testStep(this)" title="在设备上试执行本步骤,验证选择器是否命中">测试</button>':'';
// 变量按钮:选择器值里可以直接写 {本次要发的名字} —— 点它弹出来选,省得记写法
var varbtn='<button class="btn btn-xs" onclick="_stepEditor.insertVar(this)" title="把变量插进选择器值:{本次要发的名字}=本轮要发的那个号,{本机账号名字}=读到的本机账号">{变量}</button>';
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){
'<option value="on"'+(p[kVal]==='on'?' selected':'')+'>亮屏时就命中</option></select>';
}else{
valHtml='<input type="text" class="form-control" data-key="'+kVal+'" value="'+esc(p[kVal]||'')+
'" placeholder="'+(isFg?'如 com.ss.android.ugc.aweme':'如 点赞;可用 {本次要发的名字} / {本机账号名字}')+'">'+(isFg?'':pick+test);
'" placeholder="'+(isFg?'如 com.ss.android.ugc.aweme':'如 点赞;可用 {本次要发的名字} / {本机账号名字}')+'">'+(isFg?'':pick+test+varbtn);
}
var head='<div class="form-group"'+(keys.hideValue?' style="grid-column:span 3"':'')+'>'+
'<label>选择器类型</label><select class="form-control" data-key="'+kType+'"'+toggle+'>'+opts+'</select></div>';
@@ -379,7 +381,13 @@ var _stepEditor={
h+=_humanizeRowHtml(p,'<div class="form-group"><label>幅度(屏占比)</label><input type="number" class="form-control" data-key="distance_ratio" value="'+(p.distance_ratio!=null?p.distance_ratio:0.6)+'" step="0.05" min="0.1" max="0.9"></div>');
h+='<div class="help">想要"照我划的那一下原样重放",请用操作库里的《录制手势》步骤——那个是纯轨迹回放,不套这里的参数</div>';
}else if(step.type==='click'){
h+=_selRowHtml(p,'<div class="form-group"><label>等待超时(秒)</label><input type="number" class="form-control" data-key="wait_timeout" value="'+(p.wait_timeout||2)+'" step="0.5" min="0.5"></div>');
h+=_selRowHtml(p,'<div class="form-group"><label>等待超时(秒)</label><input type="number" class="form-control" data-key="wait_timeout" value="'+(p.wait_timeout||2)+'" step="0.5" min="0.5"></div>'
+'<div class="help"><b>要用"每轮都不一样的东西"点元素?</b>选择器值里可以直接写变量,'
+'点值输入框右边的 <b>{变量}</b> 按钮插入:<code>{本次要发的名字}</code>(循环轮到 / 要发的那条计划的名字)、'
+'<code>{本机账号名字}</code>(「读取文字」/「读取本机账号名字」读到的)。'
+'例:在账号列表里点自己要发的那个号 —— 类型选 <b>textContains(文本包含)</b>、值填 <code>{本次要发的名字}</code>。'
+'<br>变量那一刻还是空的(还没轮到 / 没读到)→ 值会变成空串并记一条警告,'
+'这一步就是"找不到元素",<b>不会拿 {…} 字面量去乱点</b>。</div>');
}else if(step.type==='long_click'){
h+=_selRowHtml(p,'<div class="form-row"><div class="form-group"><label>长按时长(秒)</label><input type="number" class="form-control" data-key="duration" value="'+(p.duration||1.0)+'" step="0.5" min="0.3"></div>'+
'<div class="form-group"><label>等待超时(秒)</label><input type="number" class="form-control" data-key="wait_timeout" value="'+(p.wait_timeout||2)+'" step="0.5" min="0.5"></div></div>');
@@ -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 '<div class="_varItem" data-v="'+o[0]+'" style="padding:6px 8px;cursor:pointer;border-radius:4px">'
+'<code style="font-weight:600">'+o[0]+'</code>'
+'<div style="color:#667085;margin-top:2px">'+o[1]+'</div></div>';}).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();
+5 -1
View File
@@ -968,10 +968,14 @@ 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 varbtn = (kind === 'selval' && typeof _stepEditor !== 'undefined' && _stepEditor.insertVar)
? '<button class="btn btn-xs" style="margin-left:4px" onclick="_stepEditor.insertVar(this)" title="插入变量:{本次要发的名字}=本轮要发的那个号">{变量}</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"' + ph + ' ' + attrs + ' value="'
+ esc(v == null ? '' : v) + '">' + pick + '</div></div>';
+ esc(v == null ? '' : v) + '">' + pick + varbtn + '</div></div>';
}
// 抓取元素回填后:把选择器类型下拉也切过去(通用编辑器那套要看 .sc-body,这里结构不同)