74 lines
15 KiB
HTML
74 lines
15 KiB
HTML
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<meta name="theme-color" content="#020617">
|
|
<title>Mobile Agent · 最小闭环原型</title>
|
|
<style>
|
|
:root{--bg:#020617;--card:#0f172a;--line:#1e293b;--text:#f8fafc;--muted:#94a3b8;--green:#22c55e;--green-deep:#14532d;--amber:#fbbf24;--red:#fca5a5}*{box-sizing:border-box}body{min-height:100vh;margin:0;overflow-x:hidden;background:radial-gradient(circle at 50% 0,#14532d55,transparent 34%),var(--bg);color:var(--text);font:14px/1.5 "Segoe UI","Microsoft YaHei",sans-serif}button{font:inherit;cursor:pointer}.stage{width:100%;max-width:430px;min-height:100vh;margin:auto;overflow:hidden;background:var(--bg);box-shadow:0 0 80px #000;padding-bottom:86px}.statusbar{display:flex;align-items:center;justify-content:space-between;height:30px;padding:0 20px;font-size:12px}.header{display:flex;align-items:center;justify-content:space-between;padding:18px 20px 14px}.brand{display:flex;align-items:center;gap:11px;min-width:0}.mark{display:grid;place-items:center;width:38px;height:38px;border-radius:11px;background:linear-gradient(135deg,#4ade80,#15803d);color:#052e16;font-weight:900}.brand b{display:block;font-size:17px}.brand small{color:var(--muted)}.pill,.tag{display:inline-flex;flex:none;border-radius:99px;background:#1e293b;color:#cbd5e1;font-size:11px;font-weight:800}.pill{padding:6px 10px;background:#052e16;color:#86efac;font-size:12px}.tag{padding:4px 9px}.tag.good{background:var(--green-deep);color:#bbf7d0}.tag.warn{background:#422006;color:#fde68a}.tag.bad{background:#450a0a;color:#fecaca}.view{display:none;padding:4px 16px}.view.active{display:block}.hero{margin-bottom:14px;padding:20px;border:1px solid #166534;border-radius:18px;background:linear-gradient(145deg,#052e16,#0f172a 72%)}.signal{width:11px;height:11px;margin-bottom:17px;border-radius:50%;background:var(--green);box-shadow:0 0 0 6px #14532d}.hero h1{margin:0 0 5px;font-size:23px}.muted{color:var(--muted)}.meta{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:18px}.meta div{padding:11px;border-radius:10px;background:#ffffff08}.meta small{display:block;color:var(--muted)}.meta b{display:block;margin-top:3px}.card{margin-bottom:12px;overflow:hidden;border:1px solid var(--line);border-radius:15px;background:var(--card)}.card-head{display:flex;align-items:center;justify-content:space-between;padding:15px 16px;border-bottom:1px solid var(--line)}.card-body{padding:15px 16px}.progress{height:7px;margin:15px 0;overflow:hidden;border-radius:9px;background:#1e293b}.bar{width:64%;height:100%;background:var(--green)}.steps{display:grid;gap:11px}.step{display:grid;grid-template-columns:23px minmax(0,1fr) auto;align-items:center;gap:9px}.check{display:grid;place-items:center;width:20px;height:20px;border-radius:50%;background:var(--green-deep);color:#86efac;font-size:11px}.check.now{border:2px solid #4ade80;background:transparent}.step small{color:var(--muted)}.btn{width:100%;min-height:48px;padding:0 14px;border:1px solid #334155;border-radius:11px;background:#1e293b;color:#f8fafc;font-weight:750}.btn:hover,.btn:focus-visible{outline:2px solid #4ade80;border-color:#4ade80}.btn.primary{border-color:#22c55e;background:#15803d}.row{display:flex;gap:10px}.row .btn{flex:1}.kv{display:grid;grid-template-columns:1fr auto;gap:15px;padding:12px 0;border-bottom:1px solid var(--line)}.kv:last-child{border:0}.kv b{font-size:13px}.kv span{max-width:220px;color:var(--muted);text-align:right;overflow-wrap:anywhere}.permission{display:grid;grid-template-columns:42px minmax(0,1fr) auto;align-items:center;gap:11px;padding:14px 0;border-bottom:1px solid var(--line)}.permission:last-child{border:0}.icon{display:grid;place-items:center;width:38px;height:38px;border-radius:10px;background:#1e293b;color:#4ade80;font-size:11px;font-weight:800}.log{font:12px/1.75 Consolas,monospace;color:#cbd5e1}.log div{margin:5px 0;padding-left:10px;border-left:2px solid #334155}.time{color:#64748b}.warning,.error{margin-bottom:12px;padding:13px;border-radius:10px;overflow-wrap:anywhere}.warning{border:1px solid #92400e;background:#422006;color:#fde68a}.error{border:1px solid #991b1b;background:#450a0a;color:#fecaca}.bottom{position:fixed;bottom:0;left:50%;z-index:4;display:grid;grid-template-columns:repeat(4,1fr);width:min(430px,100%);height:72px;transform:translateX(-50%);border-top:1px solid var(--line);background:#0f172af5;backdrop-filter:blur(10px)}.bottom button{min-height:58px;border:0;background:transparent;color:#64748b;font-size:12px}.bottom button.active{color:#4ade80}.bottom i{display:block;width:9px;height:9px;margin:0 auto 5px;border:2px solid currentColor;border-radius:3px}.dialog-wrap{position:fixed;inset:0;z-index:10;display:none;align-items:flex-end;justify-content:center;background:#000b}.dialog-wrap.open{display:flex}.dialog{width:min(430px,100%);padding:22px 18px 30px;border:1px solid #334155;border-radius:22px 22px 0 0;background:#0f172a}.dialog h2{margin-top:0}.toast{position:fixed;bottom:88px;left:50%;z-index:20;padding:11px 15px;transform:translate(-50%,10px);border-radius:9px;background:#f8fafc;color:#0f172a;opacity:0;transition:.2s;white-space:nowrap}.toast.show{transform:translate(-50%,0);opacity:1}
|
|
@media(min-width:600px){body{padding:28px 0}.stage{min-height:850px;border:8px solid #1e293b;border-radius:42px}.bottom{bottom:28px;border-radius:0 0 34px 34px}}
|
|
@media(prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
body{font-size:16px}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="stage">
|
|
<div class="statusbar"><span>14:32</span><span>5G · 82%</span></div>
|
|
<header class="header"><div class="brand"><div class="mark">M</div><div><b>Mobile Agent</b><small>PDD 采集执行端</small></div></div><span class="pill" id="headerState">已连接</span></header>
|
|
|
|
<section class="view active" id="home">
|
|
<div class="hero"><div class="signal"></div><h1>设备在线,正在执行</h1><div class="muted">服务端连接稳定 · 4 秒前完成心跳</div><div class="meta"><div><small>设备 ID</small><b>HUAWEI-01</b></div><div><small>注册状态</small><b>自动注册完成</b></div></div></div>
|
|
<article class="card"><div class="card-head"><div><b>设备身份</b><div class="muted">Agent 0.1.0 · installId c57a…91be</div></div><span class="tag good">Token 有效</span></div><div class="card-body"><p class="muted" style="margin-top:0">首次启动已自动提交设备信息并完成注册,不需要输入注册码或等待后台审核。</p><button class="btn" id="registrationFlow">查看首次注册流程</button></div></article>
|
|
<article class="card"><div class="card-head"><div><b>当前采集任务</b><div class="muted">T-20260814-073</div></div><span class="tag good">执行中</span></div><div class="card-body"><b>goods_id 684215908771</b><p class="muted">规则快照:PDD 商品详情</p><div class="progress"><div class="bar"></div></div><div class="steps"><div class="step"><i class="check">✓</i><div>打开 URL 快照<br><small>已进入对应 PDD 商品页</small></div><span class="tag good">完成</span></div><div class="step"><i class="check">✓</i><div>读取商品基本信息<br><small>标题、店铺、销量、评价</small></div><span class="tag good">完成</span></div><div class="step"><i class="check now"></i><div>采集规格与价格<br><small>正在读取第 2 / 3 个颜色</small></div><span class="tag warn">进行中</span></div><div class="step"><i class="check"></i><div>写回当前任务<br><small>等待执行</small></div></div></div></div></article>
|
|
<div class="warning">任务使用创建时固化的 URL、goods_id 和规则快照。采集期间不会在线读取或切换规则。</div>
|
|
<div class="row"><button class="btn" id="diagnose">连接诊断</button><button class="btn primary" data-tab="log">查看日志</button></div>
|
|
</section>
|
|
|
|
<section class="view" id="task">
|
|
<article class="card"><div class="card-head"><b>任务输入快照</b><span class="tag good">执行中</span></div><div class="card-body"><div class="kv"><b>任务 ID</b><span>T-20260814-073</span></div><div class="kv"><b>goods_id</b><span>684215908771</span></div><div class="kv"><b>URL 快照</b><span>goods2.html?goods_id=684215908771</span></div><div class="kv"><b>规则快照</b><span>PDD 商品详情</span></div><div class="kv"><b>领取方式</b><span>人工指定本设备</span></div><div class="kv"><b>租约剩余</b><span>84 秒</span></div></div></article>
|
|
<div class="error"><b>执行边界</b><br>Agent 只能完成采集任务,不执行付款、下单、绕过验证码或远程屏幕控制。</div>
|
|
<button class="btn" id="simulate">模拟:出现验证码</button>
|
|
</section>
|
|
|
|
<section class="view" id="permissions">
|
|
<article class="card"><div class="card-head"><b>必要运行条件</b><span class="tag good">全部就绪</span></div><div class="card-body"><div class="permission"><div class="icon">A11Y</div><div><b>无障碍服务</b><br><small class="muted">读取控件并执行规则白名单动作</small></div><span class="tag good">已开启</span></div><div class="permission"><div class="icon">BG</div><div><b>后台运行</b><br><small class="muted">前台服务与电池策略正常</small></div><span class="tag good">正常</span></div><div class="permission"><div class="icon">NET</div><div><b>服务端网络</b><br><small class="muted">自动注册、心跳、任务与结果接口</small></div><span class="tag good">可访问</span></div></div></article>
|
|
<p class="muted" style="padding:0 5px">Agent 不保存原始无障碍控件树和截图,也不包含实时屏幕能力。</p>
|
|
<button class="btn" id="recheck">重新检查</button>
|
|
</section>
|
|
|
|
<section class="view" id="log">
|
|
<article class="card"><div class="card-head"><b>任务日志</b><button class="tag" id="clearLog" style="border:0">清屏</button></div><div class="card-body log" id="logs"><div><span class="time">14:32:18</span> extract color=黑色 price=3290</div><div><span class="time">14:32:14</span> select color index=2/3</div><div><span class="time">14:32:06</span> dimensions color=3 size=5</div><div><span class="time">14:31:58</span> product fields extracted</div><div><span class="time">14:31:42</span> task started rule_snapshot=loaded</div><div><span class="time">14:31:38</span> claimed assigned task lease=120s</div></div></article>
|
|
<article class="card"><div class="card-head"><b>连接信息</b></div><div class="card-body"><div class="kv"><b>任务 API</b><span style="color:#4ade80">可用 · 126 ms</span></div><div class="kv"><b>最近心跳</b><span>4 秒前</span></div><div class="kv"><b>前台服务</b><span>运行中</span></div><div class="kv"><b>本地执行锁</b><span>已持有</span></div></div></article>
|
|
<button class="btn" id="reconnect">重新连接服务端</button>
|
|
</section>
|
|
|
|
<nav class="bottom" aria-label="底部导航"><button class="active" data-tab="home"><i></i>状态</button><button data-tab="task"><i></i>任务</button><button data-tab="permissions"><i></i>权限</button><button data-tab="log"><i></i>日志</button></nav>
|
|
</main>
|
|
|
|
<div class="dialog-wrap" id="diagnostic"><div class="dialog" role="dialog" aria-modal="true" aria-labelledby="diagnosticTitle"><h2 id="diagnosticTitle">连接诊断</h2><p class="muted">检查自动注册身份、任务 API 和心跳,不包含推流通道。</p><div class="kv"><b>设备认证</b><span style="color:#4ade80">令牌有效</span></div><div class="kv"><b>任务 API</b><span style="color:#4ade80">可访问</span></div><div class="kv"><b>服务器时间差</b><span>+ 0.8 秒</span></div><div class="kv"><b>网络延迟</b><span>126 ms</span></div><div class="row" style="margin-top:18px"><button class="btn" id="closeDialog">关闭</button><button class="btn primary" id="testConn">重新测试</button></div></div></div>
|
|
<div class="dialog-wrap" id="registration"><div class="dialog" role="dialog" aria-modal="true" aria-labelledby="registrationTitle"><h2 id="registrationTitle">首次启动自动注册</h2><p class="muted">客户端提交合法设备信息后,服务端立即视为已注册。</p><div class="steps"><div class="step"><i class="check">1</i><div><b>生成安装身份</b><br><small>本地持久化唯一 installId</small></div></div><div class="step"><i class="check">2</i><div><b>提交设备信息</b><br><small>型号、系统、Agent 版本和 installId</small></div></div><div class="step"><i class="check">3</i><div><b>服务端立即登记</b><br><small>创建或更新设备并返回 Device Token</small></div></div><div class="step"><i class="check">4</i><div><b>开始心跳与领任务</b><br><small>没有注册码、审核或人工确认</small></div></div></div><button class="btn primary" id="closeRegistration" style="margin-top:20px">知道了</button></div></div>
|
|
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
|
<script>
|
|
const tabs=[...document.querySelectorAll('[data-tab]')];
|
|
function show(id){document.querySelectorAll('.view').forEach(v=>v.classList.toggle('active',v.id===id));document.querySelectorAll('.bottom button').forEach(b=>b.classList.toggle('active',b.dataset.tab===id));scrollTo(0,0)}
|
|
tabs.forEach(b=>b.addEventListener('click',()=>show(b.dataset.tab)));
|
|
const dialog=document.querySelector('#diagnostic');
|
|
document.querySelector('#diagnose').addEventListener('click',()=>dialog.classList.add('open'));
|
|
document.querySelector('#closeDialog').addEventListener('click',()=>dialog.classList.remove('open'));
|
|
dialog.addEventListener('click',e=>{if(e.target===dialog)dialog.classList.remove('open')});
|
|
const registration=document.querySelector('#registration');
|
|
document.querySelector('#registrationFlow').addEventListener('click',()=>registration.classList.add('open'));
|
|
document.querySelector('#closeRegistration').addEventListener('click',()=>registration.classList.remove('open'));
|
|
registration.addEventListener('click',e=>{if(e.target===registration)registration.classList.remove('open')});
|
|
function toast(message){const t=document.querySelector('#toast');t.textContent=message;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2200)}
|
|
document.querySelector('#testConn').addEventListener('click',()=>toast('设备认证、任务 API 和心跳均正常'));
|
|
document.querySelector('#recheck').addEventListener('click',()=>toast('3 项必要条件全部通过'));
|
|
document.querySelector('#reconnect').addEventListener('click',()=>toast('已重新连接任务服务'));
|
|
document.querySelector('#clearLog').addEventListener('click',()=>{document.querySelector('#logs').innerHTML='<div><span class="time">14:33:01</span> local display cleared</div>'});
|
|
document.querySelector('#simulate').addEventListener('click',()=>{const state=document.querySelector('#headerState');state.textContent='需要处理';state.style.cssText='background:#422006;color:#fde68a';toast('任务失败:PDD_CAPTCHA_REQUIRED')});
|
|
document.addEventListener('keydown',e=>{if(e.key==='Escape')document.querySelectorAll('.dialog-wrap.open').forEach(x=>x.classList.remove('open'))});
|
|
</script>
|
|
</body>
|
|
</html>
|