docs(architecture): adopt authorized single-pass purchase
This commit is contained in:
@@ -51,6 +51,8 @@
|
||||
- **开工门槛(一次性)**:P0 的 UI 模块首次实现前应有原型;没有就先生成原型、人工确认后再拆任务。
|
||||
- **两阶段确认**:Phase 0 先确认流程、状态、主动作和布局;T-103 真机取证后再核对实际可读
|
||||
字段与文案。第二阶段若有变化,先修订 IX 与原型,再写生产页面。
|
||||
- 管理员“开始采购”授权的单趟流程属于显著改版,T-111 重新生成任务创建、工作台、详情和桌面执行
|
||||
四个原型;旧的试选后确认/第二趟状态不得继续作为实现输入。
|
||||
- **触发式重新生成**:新需求显著改变某页面的布局或控件集合时,把"重新生成该页原型 → 更新 IX 草稿"作为该任务的第一步。判断标准只有一条:这次变更是否让 agent 需要重新"看图"才能枚举交互。换文案、加字段等小改动只改 IX 条目,不碰原型。
|
||||
- **实现后即过期**:页面实现后,原型自动视为过期,不回头修补;实现后的视觉事实由任务文件 `## 执行记录` 中的真实截图或可运行验证承担。
|
||||
- 需要新原型时整页重新生成,不逐次修补旧文件。
|
||||
|
||||
@@ -398,12 +398,12 @@
|
||||
<option value="version_mismatch">未就绪 · 拼多多版本失配</option>
|
||||
<option value="idle" selected>已就绪 · 尚未轮询</option>
|
||||
<option value="polling">轮询中 · 暂无任务</option>
|
||||
<option value="trial">执行中 · 第一趟试选</option>
|
||||
<option value="trial_done">第一趟完成 · 已释放手机</option>
|
||||
<option value="dry_run">执行中 · 下单只读演练</option>
|
||||
<option value="dry_run_ready">演练完成 · 等待真实第二趟</option>
|
||||
<option value="order_prefence">真实第二趟 · 围栏前校验</option>
|
||||
<option value="fencing">真实第二趟 · 申请围栏中</option>
|
||||
<option value="claimed">单趟执行 · 已领取</option>
|
||||
<option value="selecting">单趟执行 · 选择规格</option>
|
||||
<option value="gate1">单趟执行 · 闸门一</option>
|
||||
<option value="quantity_gate2">单趟执行 · 数量与闸门二</option>
|
||||
<option value="confirm_gate3">单趟执行 · 确认页与闸门三</option>
|
||||
<option value="fencing">单趟执行 · 申请围栏中</option>
|
||||
<option value="fence_failed">围栏失败或响应不明 · 不点击</option>
|
||||
<option value="fenced">围栏后 · 核对同一提交</option>
|
||||
<option value="waiting_payment">明确已创建 · 等人付款</option>
|
||||
@@ -469,7 +469,7 @@
|
||||
<div class="span-2"><dt>商品</dt><dd id="product">暂无商品;轮询会在安全就绪后领取一条任务</dd></div>
|
||||
<div><dt>颜色分类</dt><dd id="color">—</dd></div>
|
||||
<div><dt>尺码 / 数量</dt><dd id="size-qty">—</dd></div>
|
||||
<div><dt>授权单价</dt><dd id="auth-price">—</dd></div>
|
||||
<div><dt>闸门一单价</dt><dd id="gate1-price">—</dd></div>
|
||||
<div><dt>金额上限</dt><dd id="price-cap">—</dd></div>
|
||||
<div class="span-2"><dt>提交围栏</dt><dd id="submission">未建立</dd></div>
|
||||
</dl>
|
||||
@@ -583,41 +583,41 @@
|
||||
const task = {
|
||||
id: 'CB-20260803-0042',
|
||||
product: '纯棉圆领短袖 T 恤(原型假数据)',
|
||||
color: '白色', sizeQty: 'XL / 2 件', authPrice: 'CNY 32.50', cap: 'CNY 80.00'
|
||||
color: '白色', sizeQty: 'XL / 2 件', gate1Price: 'CNY 32.50', cap: 'CNY 80.00'
|
||||
};
|
||||
const records = [
|
||||
{
|
||||
id:'REC-DEMO-0042', taskId:'CB-20260803-0042', title:'纯棉圆领短袖 T 恤(原型假数据)', collectedAt:'2026-08-03T13:52:18+08:00',
|
||||
status:'RECONCILIATION_REQUIRED', spec:'白色 | XL | 2 件', cap:'CNY 80.00', submission:'SUB-DEMO-0042 · 结果不明确',
|
||||
note:'围栏后网络中断,订单可能已创建。只允许核查同一提交记录,不能重试。', evidence:['商品页截图裁剪(内联假证据)','规格面板截图(内联假证据)'],
|
||||
timeline:['13:51:04 重新选择同一规格','13:51:41 三道价格闸门通过(假数据)','13:51:58 围栏已建立','13:52:18 点击结果不明确,转人工核查'],
|
||||
note:'围栏后网络中断,订单可能已创建。只允许核查同一提交记录,不能重试。', evidence:['商品页原始截图(内联假证据)','规格面板原始截图(内联假证据)'],
|
||||
timeline:['13:51:04 同一趟精确选择规格','13:51:41 三道价格闸门通过(假数据)','13:51:58 围栏已建立','13:52:18 点击结果不明确,转人工核查'],
|
||||
result:'下单待核查:白色 | XL | CNY 32.50 | 2 件', resultNote:'订单可能已创建、支付未完成。系统不会再次提交,也不会付款。'
|
||||
},
|
||||
{
|
||||
id:'REC-DEMO-0041', taskId:'CB-20260803-0041', title:'办公室桌面收纳盒(原型假数据)', collectedAt:'2026-08-03T12:24:09+08:00',
|
||||
status:'WAITING_CONFIRMATION', spec:'透明 | L | 2 件', cap:'CNY 60.00', submission:'未建立',
|
||||
note:'第一趟试选完成,规格面板证据已回传,等待管理员确认。', evidence:['商品页截图裁剪(内联假证据)','规格面板截图(内联假证据)'],
|
||||
timeline:['12:22:31 打开商品','12:23:04 匹配透明 / L','12:23:38 读取规格面板单价','12:24:09 截图并退出商品页'],
|
||||
result:'试选:透明 | L | CNY 24.00 | 2 件', resultNote:'这只是试选结果,不代表已下单。'
|
||||
status:'ORDERING', spec:'透明 | L | 2 件', cap:'CNY 60.00', submission:'未建立',
|
||||
note:'管理员已开始采购,当前单趟已完成规格与闸门一,正在设置并复核数量。', evidence:['商品页截图(内联假证据)','规格面板原始截图(内联假证据)'],
|
||||
timeline:['12:22:31 领取一次性授权','12:23:04 精确匹配透明 / L','12:23:38 闸门一读取 CNY 24.00','12:24:09 正在复核数量与闸门二'],
|
||||
result:'执行中:透明 | L | CNY 24.00 | 2 件', resultNote:'无需等待管理员中途确认;任一门禁失败即停止。'
|
||||
},
|
||||
{
|
||||
id:'REC-DEMO-0038', taskId:'CB-20260803-0038', title:'标签打印纸补货(原型假数据)', collectedAt:'2026-08-03T10:06:42+08:00',
|
||||
status:'WAITING_PAYMENT', spec:'白色 | 50×30 | 1 件', cap:'CNY 50.00', submission:'SUB-DEMO-0038 · SUBMITTED',
|
||||
note:'已明确创建待付款订单,自动化已停止,等待人核对后自行付款。', evidence:['订单确认页非敏感截图(内联假证据)'],
|
||||
timeline:['10:04:22 重新选择同一规格','10:05:10 价格与数量复核通过','10:05:36 获得唯一围栏许可','10:06:42 明确创建待付款订单'],
|
||||
note:'已明确创建待付款订单,自动化已停止,等待人核对后自行付款。', evidence:['订单确认页内部原始截图(内联假证据)'],
|
||||
timeline:['10:04:22 同一趟精确选择规格','10:05:10 两次价格与数量复核通过','10:05:36 获得唯一围栏许可','10:06:42 明确创建待付款订单'],
|
||||
result:'已创建待付款订单:白色 | 50×30 | CNY 46.00 | 1 件', resultNote:'系统只创建待付款订单,付款始终由人完成。'
|
||||
},
|
||||
{
|
||||
id:'REC-DEMO-0032', taskId:'CB-20260803-0032', title:'仓库打包胶带(原型假数据)', collectedAt:'2026-08-03T09:18:05+08:00',
|
||||
status:'NEEDS_MANUAL', spec:'透明 | 45mm | 4 件', cap:'CNY 55.00', submission:'未建立',
|
||||
note:'商品页面未取得可信图片,授权价与当前规格面板价格不一致,已转人工。', evidence:[],
|
||||
note:'设置数量后规格面板价格与闸门一不一致,已在围栏前转人工。', evidence:[],
|
||||
timeline:['09:16:40 打开商品','09:17:22 匹配透明 / 45mm','09:18:05 价格闸门二不通过,停止自动流程'],
|
||||
result:'未采购:授权价 CNY 12.00 | 当前价 CNY 13.50', resultNote:'没有创建订单,也没有付款。'
|
||||
result:'未采购:闸门一 CNY 12.00 | 闸门二 CNY 13.50', resultNote:'没有建立围栏、创建订单或付款。'
|
||||
}
|
||||
].sort((a, b) => b.collectedAt.localeCompare(a.collectedAt));
|
||||
const recordStatus = {
|
||||
RECONCILIATION_REQUIRED:{label:'结果待核查',tone:'critical'},
|
||||
WAITING_CONFIRMATION:{label:'等待确认',tone:'caution'},
|
||||
ORDERING:{label:'采购执行中',tone:'info'},
|
||||
WAITING_PAYMENT:{label:'待付款',tone:'success'},
|
||||
NEEDS_MANUAL:{label:'转人工',tone:'caution'}
|
||||
};
|
||||
@@ -627,40 +627,38 @@
|
||||
let detailRequestId = 0;
|
||||
const baseSteps = {
|
||||
idle: [],
|
||||
trial: ['打开商品(待真机判据)', '精确匹配颜色分类与尺码', '从规格面板读取单价', '截图并退出商品页'],
|
||||
dry: ['重新打开商品(待真机判据)', '精确选择与数量复核', '读取订单确认页非敏感摘要', '验证提交控件唯一后退出'],
|
||||
order: ['重新选择同一规格', '闸门二:单价必须等于授权价', '数量复核与闸门三', '申请服务端提交围栏', '围栏许可后内部仅点击一次', '调和同一提交结果']
|
||||
single: ['核对一次性授权并打开商品', '精确选择颜色分类与尺码', '闸门一:读取 SKU 单价并校验上限', '设置数量、读回并执行闸门二', '确认页执行闸门三与控件唯一校验', '申请服务端提交围栏', '首次许可后持久化并内部点击一次', '调和同一提交结果']
|
||||
};
|
||||
const scenarios = {
|
||||
not_ready: { tone:'critical', title:'web 服务不可达', text:'未开始轮询。检查本地网络和服务地址;已完成的本地证据会保留待补传。', web:'bad', device:'ok', version:'ok', leg:'未就绪', legTone:'critical', action:'开始轮询', enabled:false, reason:'web 服务未就绪,不能领取任务。', session:'未启动', step:'连接检查', kind:'idle', active:-1, countdown:'—', fail:'1 / 3' },
|
||||
version_mismatch: { tone:'critical', title:'拼多多版本与证据不一致', text:'已 fail closed:停止领取真机任务。下一步是由人重新取证并记录 App 版本,不能沿用旧判据。', web:'ok', device:'ok', version:'bad', leg:'版本失配', legTone:'critical', action:'开始轮询', enabled:false, reason:'App 版本失配;必须先完成本项目真机取证。', session:'已停止', step:'等待重新取证', kind:'idle', active:-1, countdown:'—', fail:'0 / 3' },
|
||||
idle: { tone:'success', title:'设备已就绪(模拟)', text:'三项就绪条件在本场景中均为模拟通过,可以演示定时轮询;不会连接真实设备。', web:'ok', device:'ok', version:'ok', leg:'空闲', legTone:'success', action:'开始轮询', enabled:true, reason:'点击只切换原型状态,不发起网络请求。', session:'未启动', step:'等待开始', kind:'idle', active:-1, countdown:'—', fail:'0 / 3' },
|
||||
polling: { tone:'info', title:'正在轮询,暂无待领任务', text:'无任务是正常空状态,不计入失败;将按配置间隔继续检查。', web:'ok', device:'ok', version:'ok', leg:'轮询中', legTone:'info', action:'停止轮询', enabled:true, reason:'停止后不再领取新任务。', session:'运行中', step:'等待下一轮', kind:'idle', active:-1, countdown:'00:12', fail:'0 / 3', count:2 },
|
||||
trial: { tone:'info', title:'第一趟试选正在执行', text:'只勾选规格、读价和截图;不会进入订单确认流程,完成后必须退出商品页释放手机。', web:'ok', device:'ok', version:'ok', leg:'TRIAL · 第一趟试选', legTone:'info', action:'真机步骤执行中', enabled:false, reason:'真机步骤期间禁用硬取消和关闭,避免留下无法判定的中间态。', session:'运行中', step:'读取规格面板单价', kind:'trial', active:2, countdown:'暂停', fail:'0 / 3', lease:'04:18' },
|
||||
trial_done: { tone:'success', title:'试选已回传,手机已释放', text:'规格与价格证据已保存(模拟);任务等待管理员确认,本设备继续下一轮。', web:'ok', device:'ok', version:'ok', leg:'TRIAL · 已完成', legTone:'success', action:'停止轮询', enabled:true, reason:'手机已退出商品页,可以安全停止轮询。', session:'运行中', step:'等待下一轮', kind:'trial', active:4, countdown:'00:09', fail:'0 / 3', count:3 },
|
||||
dry_run: { tone:'info', title:'DRY-RUN · 只读演练', text:'只到订单确认页读取非敏感摘要并验证控件唯一;不会提交订单,也不会消费授权。', web:'ok', device:'ok', version:'ok', leg:'DRY-RUN · 不会提交', legTone:'info', action:'只读演练进行中', enabled:false, reason:'演练中不提供提交动作;真机步骤期间受控关闭。', session:'运行中', step:'读取订单确认页', kind:'dry', active:2, countdown:'暂停', fail:'0 / 3', lease:'03:42' },
|
||||
dry_run_ready: { tone:'success', title:'只读演练已完成', text:'演练证据已回传(模拟)并退出确认页。旧演练不能替代真实第二趟的当前页面事实。', web:'ok', device:'ok', version:'ok', leg:'DRY-RUN · READY', legTone:'success', action:'停止轮询', enabled:true, reason:'等待服务端安排真实第二趟;不会由用户直接触发提交。', session:'运行中', step:'等待真实第二趟', kind:'dry', active:4, countdown:'00:16', fail:'0 / 3', count:4 },
|
||||
order_prefence: { tone:'caution', title:'真实第二趟 · 围栏前校验', text:'正在重新验证授权、闸门二、数量、闸门三和控件唯一。此时尚无点击许可。', web:'ok', device:'ok', version:'ok', leg:'ORDER · 围栏前', legTone:'caution', action:'安全校验进行中', enabled:false, reason:'没有服务端围栏许可,绝不能点击提交订单。', session:'运行中', step:'闸门三:核对实付款', kind:'order', active:2, countdown:'暂停', fail:'0 / 3', lease:'02:58' },
|
||||
fencing: { tone:'caution', title:'正在申请服务端提交围栏', text:'等待明确的 click_permitted。超时、冲突、网络失败或响应不明都不会点击。', web:'ok', device:'ok', version:'ok', leg:'ORDER · 申请围栏', legTone:'caution', action:'等待围栏响应', enabled:false, reason:'申请中禁止取消、关闭或重复申请。', session:'运行中', step:'申请唯一提交围栏', kind:'order', active:3, countdown:'暂停', fail:'0 / 3', lease:'02:31' },
|
||||
fence_failed: { tone:'critical', title:'未获得点击许可', text:'围栏申请失败或响应不明:没有点击提交订单。请按幂等键核查服务端记录,不能直接重试。', web:'bad', device:'ok', version:'ok', leg:'ORDER · 未获许可', legTone:'critical', action:'打开围栏核查说明', enabled:true, safeAction:'review', reason:'只核查围栏状态,不会触发提交。', session:'已停止', step:'核查幂等键', kind:'order', active:3, countdown:'—', fail:'1 / 3', submission:'围栏状态未知 · 幂等键 DEMO-FENCE-042' },
|
||||
fenced: { tone:'caution', title:'围栏已建立,只核对同一提交', text:'submission_id 已固定。内部流程至多点击一次;用户界面不提供提交、重领、放弃或重试入口。', web:'ok', device:'ok', version:'ok', leg:'ORDER · 已围栏', legTone:'caution', action:'打开同一提交核查说明', enabled:true, safeAction:'review', reason:'只能查看与调和同一提交记录。', session:'受控执行', step:'调和订单结果', kind:'order', active:5, countdown:'暂停', fail:'0 / 3', lease:'01:49', submission:'SUB-DEMO-0042 · FENCED' },
|
||||
waiting_payment: { tone:'success', title:'已明确创建待付款订单', text:'自动化已停止。系统不会付款;请人在拼多多核对后自行付款,再到采购服务标记。', web:'ok', device:'ok', version:'ok', leg:'WAITING_PAYMENT', legTone:'success', action:'查看待付款说明', enabled:true, safeAction:'paymentInfo', reason:'仅显示人工收口说明,不执行支付。', session:'已停止', step:'等待人工付款', kind:'order', active:6, countdown:'—', fail:'0 / 3', count:5, submission:'SUB-DEMO-0042 · SUBMITTED' },
|
||||
uncertain: { tone:'critical', title:'订单可能已创建,结果不明确', text:'保持金额预留和永久围栏,只核查 SUB-DEMO-0042;禁止重新领取、放弃授权或再次点击。', web:'ok', device:'ok', version:'ok', leg:'RECONCILIATION_REQUIRED', legTone:'critical', action:'打开人工核查说明', enabled:true, safeAction:'review', reason:'核查不会发起新的下单。可另行导出假证据摘要。', session:'已停止', step:'人工核查同一提交', kind:'order', active:5, countdown:'—', fail:'0 / 3', submission:'SUB-DEMO-0042 · 结果不明确' },
|
||||
security: { tone:'critical', title:'检测到安全校验,已停止', text:'保留现场并转人工;不尝试绕过验证码、风控、人脸或短信校验。若已围栏,只调和同一提交。', web:'ok', device:'ok', version:'ok', leg:'待人工 · 安全校验', legTone:'critical', action:'打开人工核查说明', enabled:true, safeAction:'review', reason:'只显示核查说明,不提供绕过或继续入口。', session:'已停止', step:'等待人工处理', kind:'order', active:5, countdown:'—', fail:'1 / 3', submission:'SUB-DEMO-0042 · RECONCILIATION_REQUIRED' },
|
||||
payment_handoff: { tone:'critical', title:'进入外部支付交接,已停止', text:'订单可能已创建、支付未完成。不会读取或输入支付凭据,也没有“继续付款”按钮。', web:'ok', device:'ok', version:'ok', leg:'待人工 · 外部支付', legTone:'critical', action:'打开人工核查说明', enabled:true, safeAction:'paymentInfo', reason:'只显示人工核查与付款边界。', session:'已停止', step:'人工核查同一提交', kind:'order', active:5, countdown:'—', fail:'0 / 3', submission:'SUB-DEMO-0042 · HANDED_OFF' },
|
||||
manual: { tone:'caution', title:'任务已转人工:授权价与现价不一致', text:'授权 CNY 32.50,规格面板现价 CNY 35.00。没有下单,也不会自动放弃;请联系管理员处理。', web:'ok', device:'ok', version:'ok', leg:'NEEDS_MANUAL', legTone:'caution', action:'导出假诊断摘要', enabled:true, safeAction:'export', reason:'导出仅生成本地假文本,不包含页面全文或敏感信息。', session:'继续轮询其他任务', step:'当前任务已移交', kind:'order', active:1, countdown:'00:11', fail:'0 / 3', submission:'未建立' },
|
||||
claimed: { tone:'info', title:'已领取管理员授权的采购任务', text:'任务字段与最高总价已锁定;当前只准备打开目标商品,尚未建立提交围栏。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 已领取', legTone:'info', action:'真机步骤执行中', enabled:false, reason:'当前任务已开始,停止轮询只应阻止下一次领取,不能硬取消本任务。', session:'运行中', step:'核对授权与商品', kind:'single', active:0, countdown:'暂停', fail:'0 / 3', lease:'04:48' },
|
||||
selecting: { tone:'info', title:'正在精确选择颜色与尺码', text:'只在已取证维度容器内精确唯一匹配并读回;缺失、重复或版本变化立即停止。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 选择规格', legTone:'info', action:'真机步骤执行中', enabled:false, reason:'当前受控步骤不能被硬取消。', session:'运行中', step:'精确选择规格', kind:'single', active:1, countdown:'暂停', fail:'0 / 3', lease:'04:18' },
|
||||
gate1: { tone:'info', title:'闸门一:读取 SKU 单价', text:'当前规格单价必须唯一可读,单价乘数量不得超过管理员授权的最高总价。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 闸门一', legTone:'info', action:'安全校验进行中', enabled:false, reason:'价格不可读或超上限时立即停止。', session:'运行中', step:'闸门一:读取 SKU 单价', kind:'single', active:2, countdown:'暂停', fail:'0 / 3', lease:'03:58' },
|
||||
quantity_gate2: { tone:'info', title:'数量复核与闸门二', text:'数量必须读回为 2;目标规格不能漂移,第二次单价必须等于闸门一 CNY 32.50。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 闸门二', legTone:'info', action:'安全校验进行中', enabled:false, reason:'数量、规格或价格变化都停止。', session:'运行中', step:'数量读回与闸门二', kind:'single', active:3, countdown:'暂停', fail:'0 / 3', lease:'03:31' },
|
||||
confirm_gate3: { tone:'caution', title:'确认页与闸门三', text:'正在核对规格、数量、应付总额与提交控件唯一;此时没有点击许可。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 闸门三', legTone:'caution', action:'安全校验进行中', enabled:false, reason:'没有服务端围栏许可,绝不能点击提交订单。', session:'运行中', step:'闸门三:核对应付总额', kind:'single', active:4, countdown:'暂停', fail:'0 / 3', lease:'02:58' },
|
||||
fencing: { tone:'caution', title:'正在申请服务端提交围栏', text:'等待首次明确的 click_permitted。超时、冲突、网络失败或响应不明都不会点击。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 申请围栏', legTone:'caution', action:'等待围栏响应', enabled:false, reason:'申请中禁止取消、关闭或换 key 重复申请。', session:'运行中', step:'申请唯一提交围栏', kind:'single', active:5, countdown:'暂停', fail:'0 / 3', lease:'02:31' },
|
||||
fence_failed: { tone:'critical', title:'未获得首次明确点击许可', text:'围栏失败或响应不明:没有点击提交订单。只核查同一幂等键,不能直接重试。', web:'bad', device:'ok', version:'ok', leg:'单趟 · 未获许可', legTone:'critical', action:'打开围栏核查说明', enabled:true, safeAction:'review', reason:'只核查围栏状态,不触发提交。', session:'已停止', step:'核查幂等键', kind:'single', active:5, countdown:'—', fail:'1 / 3', submission:'围栏状态未知 · 幂等键 DEMO-FENCE-042' },
|
||||
fenced: { tone:'caution', title:'围栏已建立,只核对同一提交', text:'submission_id 已固定。内部流程至多点击一次;界面不提供提交、重领、放弃或重试。', web:'ok', device:'ok', version:'ok', leg:'单趟 · 已围栏', legTone:'caution', action:'打开同一提交核查说明', enabled:true, safeAction:'review', reason:'只能查看与调和同一提交记录。', session:'受控执行', step:'调和订单结果', kind:'single', active:7, countdown:'暂停', fail:'0 / 3', lease:'01:49', submission:'SUB-DEMO-0042 · FENCED' },
|
||||
waiting_payment: { tone:'success', title:'已明确创建待付款订单', text:'自动化已停止。系统不会付款;请人在拼多多核对后自行付款,再到采购服务标记。', web:'ok', device:'ok', version:'ok', leg:'WAITING_PAYMENT', legTone:'success', action:'查看待付款说明', enabled:true, safeAction:'paymentInfo', reason:'仅显示人工收口说明,不执行支付。', session:'已停止', step:'等待人工付款', kind:'single', active:8, countdown:'—', fail:'0 / 3', count:5, submission:'SUB-DEMO-0042 · SUBMITTED' },
|
||||
uncertain: { tone:'critical', title:'订单可能已创建,结果不明确', text:'保持金额预留和永久围栏,只核查 SUB-DEMO-0042;禁止重新领取、释放授权或再次点击。', web:'ok', device:'ok', version:'ok', leg:'RECONCILIATION_REQUIRED', legTone:'critical', action:'打开人工核查说明', enabled:true, safeAction:'review', reason:'核查不会发起新的下单。可另行导出假证据摘要。', session:'已停止', step:'人工核查同一提交', kind:'single', active:7, countdown:'—', fail:'0 / 3', submission:'SUB-DEMO-0042 · 结果不明确' },
|
||||
security: { tone:'critical', title:'检测到安全校验,已停止', text:'不绕过验证码、风控、人脸或短信。若已围栏,只调和同一提交;围栏前转人工。', web:'ok', device:'ok', version:'ok', leg:'待人工 · 安全校验', legTone:'critical', action:'打开人工核查说明', enabled:true, safeAction:'review', reason:'只显示核查说明,不提供绕过或继续。', session:'已停止', step:'等待人工处理', kind:'single', active:7, countdown:'—', fail:'1 / 3', submission:'SUB-DEMO-0042 · RECONCILIATION_REQUIRED' },
|
||||
payment_handoff: { tone:'critical', title:'进入外部支付交接,已停止', text:'订单可能已创建、支付未完成。不会读取或输入支付凭据,也没有“继续付款”按钮。', web:'ok', device:'ok', version:'ok', leg:'待人工 · 外部支付', legTone:'critical', action:'打开人工核查说明', enabled:true, safeAction:'paymentInfo', reason:'只显示人工核查与付款边界。', session:'已停止', step:'人工核查同一提交', kind:'single', active:7, countdown:'—', fail:'0 / 3', submission:'SUB-DEMO-0042 · HANDED_OFF' },
|
||||
manual: { tone:'caution', title:'任务已转人工:闸门二价格变化', text:'闸门一 CNY 32.50,数量设置后重读 CNY 35.00。围栏未建立,没有创建订单;需管理员核查后重置。', web:'ok', device:'ok', version:'ok', leg:'NEEDS_MANUAL', legTone:'caution', action:'导出假诊断摘要', enabled:true, safeAction:'export', reason:'导出只生成本地假文本,不含完整 XML、页面全文或敏感字段。', session:'继续轮询其他任务', step:'当前任务已移交', kind:'single', active:3, countdown:'00:11', fail:'0 / 3', submission:'未建立' },
|
||||
failed_stop: { tone:'critical', title:'连续失败达到阈值,轮询已停止', text:'最近三次均为服务连接失败。检查设置和网络;修复后重新开始普通轮询,不会重试任何提交点击。', web:'bad', device:'ok', version:'ok', leg:'自动停止', legTone:'critical', action:'前往配置', enabled:true, safeAction:'settings', reason:'先修复连接;不会自动恢复轮询。', session:'已停止', step:'等待修复', kind:'idle', active:-1, countdown:'—', fail:'3 / 3', count:4 }
|
||||
};
|
||||
const sessionControls = {
|
||||
not_ready:{label:'开始轮询',enabled:false,reason:'web 服务未就绪,不能领取任务。'},
|
||||
version_mismatch:{label:'开始轮询',enabled:false,reason:'App 版本失配,必须先完成本项目真机取证。'},
|
||||
idle:{label:'开始轮询',enabled:true,reason:'只启动普通轮询,不会直接执行下单。'},
|
||||
idle:{label:'开始轮询',enabled:true,reason:'只启动领取会话;只有已获管理员开始采购授权的任务可执行。'},
|
||||
polling:{label:'停止轮询',enabled:true,reason:'停止后不再领取新任务。'},
|
||||
trial:{label:'停止轮询',enabled:false,reason:'真机步骤执行中,禁止硬停止。'},
|
||||
trial_done:{label:'停止轮询',enabled:true,reason:'手机已退出商品页,可以安全停止轮询。'},
|
||||
dry_run:{label:'停止轮询',enabled:false,reason:'只读演练执行中,禁止硬停止。'},
|
||||
dry_run_ready:{label:'停止轮询',enabled:true,reason:'演练已退出确认页,可以安全停止轮询。'},
|
||||
order_prefence:{label:'停止轮询',enabled:false,reason:'真实第二趟校验中,禁止硬停止。'},
|
||||
claimed:{label:'停止轮询',enabled:false,reason:'当前任务已领取;停止只应阻止下次领取,不能硬取消本任务。'},
|
||||
selecting:{label:'停止轮询',enabled:false,reason:'受控真机步骤执行中,禁止硬停止。'},
|
||||
gate1:{label:'停止轮询',enabled:false,reason:'价格闸门执行中,禁止硬停止。'},
|
||||
quantity_gate2:{label:'停止轮询',enabled:false,reason:'数量与闸门二执行中,禁止硬停止。'},
|
||||
confirm_gate3:{label:'停止轮询',enabled:false,reason:'确认页只允许安全校验,禁止硬停止或额外点击。'},
|
||||
fencing:{label:'停止轮询',enabled:false,reason:'围栏申请中,禁止关闭、取消或重复申请。'},
|
||||
fence_failed:{label:'开始轮询',enabled:false,reason:'先核查围栏状态;不能用重新轮询绕过不明确结果。'},
|
||||
fenced:{label:'停止轮询',enabled:false,reason:'已建立围栏,只能调和同一提交。'},
|
||||
@@ -836,10 +834,10 @@
|
||||
$('task-id').textContent = hasTask ? task.id : '—'; $('lease').textContent = s.lease || '—';
|
||||
$('product').textContent = hasTask ? task.product : '暂无商品;轮询会在安全就绪后领取一条任务';
|
||||
$('color').textContent = hasTask ? task.color : '—'; $('size-qty').textContent = hasTask ? task.sizeQty : '—';
|
||||
$('auth-price').textContent = hasTask && s.kind !== 'trial' ? task.authPrice : '—'; $('price-cap').textContent = hasTask ? task.cap : '—';
|
||||
$('gate1-price').textContent = hasTask && ['gate1','quantity_gate2','confirm_gate3','fencing','fence_failed','fenced','waiting_payment','uncertain','security','payment_handoff','manual'].includes($('scenario').value) ? task.gate1Price : '—'; $('price-cap').textContent = hasTask ? task.cap : '—';
|
||||
$('submission').textContent = s.submission || '未建立';
|
||||
$('preview-empty').hidden = hasTask; $('product-preview-image').hidden = !hasTask;
|
||||
$('preview-source').textContent = hasTask ? '来源:商品页截图裁剪(内联假证据)' : '来源:尚无可信图片';
|
||||
$('preview-source').textContent = hasTask ? '来源:内部原始商品截图(内联假证据)' : '来源:尚无可信图片';
|
||||
$('preview-time').textContent = hasTask ? '采集时间:2026-08-03 13:51(假数据)' : '采集时间:—';
|
||||
renderSteps(s.kind, s.active);
|
||||
$('countdown').textContent = s.countdown; $('failure-count').textContent = s.fail; $('completed-count').textContent = String(s.count || 0);
|
||||
@@ -888,7 +886,7 @@
|
||||
window.addEventListener('resize', hideRecordMenu);
|
||||
$('export-log').addEventListener('click', exportDemo);
|
||||
$('close-demo').addEventListener('click', () => {
|
||||
const unsafe = ['trial','dry_run','order_prefence','fencing','fenced'].includes($('scenario').value);
|
||||
const unsafe = ['claimed','selecting','gate1','quantity_gate2','confirm_gate3','fencing','fenced'].includes($('scenario').value);
|
||||
showDialog(unsafe ? '当前不能直接关闭' : '关闭窗口协议', unsafe ? '真机或围栏流程处于受控阶段。原生实现需先完成安全停靠或保持同一 submission 调和,不能让关闭看起来等于“可以重来”。' : '原生实现关闭窗口后立即停止普通轮询,不留后台进程。此 HTML 只演示规则,不会关闭浏览器。');
|
||||
});
|
||||
$('dialog-close').addEventListener('click', () => $('info-dialog').close());
|
||||
|
||||
@@ -203,11 +203,11 @@
|
||||
<h2>创建后发生什么</h2>
|
||||
<ol class="steps">
|
||||
<li>任务先保存为「待开始」。</li>
|
||||
<li>在任务表格勾选后统一点击「开始试选」。</li>
|
||||
<li>第一趟只试选、读价、截图,然后退出商品。</li>
|
||||
<li>你看过证据后再决定是否授权。</li>
|
||||
<li>在任务表格勾选后点击「开始采购(只创建待付款订单)」。</li>
|
||||
<li>该点击锁定商品、规格、数量和最高总价,并授权采购工具执行。</li>
|
||||
<li>采购工具同一趟校验规格、价格和数量后,只创建待付款订单。</li>
|
||||
</ol>
|
||||
<p style="margin-top:10px"><strong>系统不会自动付款。</strong></p>
|
||||
<p style="margin-top:10px"><strong>创建任务本身不授权;系统任何时候都不会自动付款。</strong></p>
|
||||
</section>
|
||||
<section class="prototype-controls">
|
||||
<h2>原型状态</h2>
|
||||
@@ -310,7 +310,7 @@
|
||||
setReady();
|
||||
form.hidden = true;
|
||||
status.className = 'status success show';
|
||||
status.innerHTML = '<h2>任务已创建</h2><p>任务编号 <strong>T-20260803-019</strong>,当前为「待开始」。请回到任务表格勾选后点击“开始试选”;系统尚未进行任何拼多多操作。</p><a class="button button-primary" href="web-task-workbench.html">查看任务工作台</a>';
|
||||
status.innerHTML = '<h2>任务已创建</h2><p>任务编号 <strong>T-20260803-019</strong>,当前为「待开始」。请回到任务表格勾选后点击“开始采购(只创建待付款订单)”;创建本身尚未授权,也没有进行任何拼多多操作。</p><a class="button button-primary" href="web-task-workbench.html">查看任务工作台</a>';
|
||||
status.focus?.();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -191,8 +191,8 @@
|
||||
|
||||
<main id="main">
|
||||
<header class="page-head">
|
||||
<div><h1>采购任务</h1><p class="subtitle">创建后先保存为待开始;勾选后统一进入第一趟试选队列。</p></div>
|
||||
<p class="safety-note"><strong>只开始试选,不会下单。</strong> 下单必须在试选完成并经人工确认后另行授权,付款始终由人完成。</p>
|
||||
<div><h1>采购任务</h1><p class="subtitle">创建后先保存为待开始;勾选并开始采购后,采购工具按授权逐条执行。</p></div>
|
||||
<p class="safety-note"><strong>开始采购会授权创建待付款订单。</strong> 系统校验规格、数量和金额后最多提交一次,任何时候都不会自动付款。</p>
|
||||
</header>
|
||||
|
||||
<section class="workbench" aria-label="采购任务表格工作台">
|
||||
@@ -213,13 +213,13 @@
|
||||
<form class="filter-form" id="filter-form" role="search">
|
||||
<div class="filter-field"><label for="keyword">标题关键词</label><input id="keyword" type="search" placeholder="输入采购任务标题" autocomplete="off"></div>
|
||||
<div class="filter-actions">
|
||||
<details class="advanced-filters" id="advanced-filters"><summary>更多条件</summary><div class="filter-popover"><div><label for="status-filter">状态</label><select id="status-filter"><option value="all">全部状态</option><option value="DRAFT">待开始</option><option value="PENDING">待领取</option><option value="WAITING_CONFIRMATION">等待确认</option><option value="WAITING_PAYMENT">待付款</option><option value="RECONCILIATION_REQUIRED">结果待核查</option><option value="SUCCEEDED">已完成</option></select></div><div><label for="time-filter">创建时间</label><select id="time-filter"><option value="all">全部时间</option><option value="today">今天</option><option value="week">最近 7 天</option></select></div></div></details>
|
||||
<details class="advanced-filters" id="advanced-filters"><summary>更多条件</summary><div class="filter-popover"><div><label for="status-filter">状态</label><select id="status-filter"><option value="all">全部状态</option><option value="DRAFT">待开始</option><option value="PENDING">已授权待领取</option><option value="ORDERING">采购执行中</option><option value="NEEDS_MANUAL">待人工处理</option><option value="WAITING_PAYMENT">待付款</option><option value="RECONCILIATION_REQUIRED">结果待核查</option><option value="SUCCEEDED">已完成</option></select></div><div><label for="time-filter">创建时间</label><select id="time-filter"><option value="all">全部时间</option><option value="today">今天</option><option value="week">最近 7 天</option></select></div></div></details>
|
||||
<div class="toolbar-group"><button class="button button-primary" type="submit">筛选</button><button class="button button-secondary" id="clear-filter" type="button">清除</button></div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="selection-mode" id="selection-mode" hidden>
|
||||
<div class="selection-copy"><strong id="selection-count">已选择 0 条</strong><span>只会加入第一趟试选队列,不签发授权、不创建订单、不付款。</span></div>
|
||||
<div class="toolbar-group"><button class="button button-primary" id="start-trial" type="button">开始试选</button><button class="button button-secondary" id="clear-selection" type="button">清除选择</button></div>
|
||||
<div class="selection-copy"><strong id="selection-count">已选择 0 条</strong><span id="selection-cap">最高总额合计 ¥0.00</span><span>点击将授权采购工具逐条创建待付款订单;系统不会付款。</span></div>
|
||||
<div class="toolbar-group"><button class="button button-primary" id="start-purchase" type="button">开始采购(只创建待付款订单)</button><button class="button button-secondary" id="clear-selection" type="button">清除选择</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -268,7 +268,7 @@
|
||||
<section class="drawer-panel"><h3>采购要求</h3><dl class="detail-grid" id="drawer-requirements"></dl></section>
|
||||
<section class="drawer-panel"><h3>采购结果</h3><div id="drawer-result"></div></section>
|
||||
<section class="drawer-panel"><h3>当前状态与下一步</h3><div class="next-step" id="drawer-next-step"></div></section>
|
||||
<section class="drawer-panel"><h3>执行证据</h3><div class="evidence-placeholder"><div><strong>假数据证据占位</strong><br>真实可读字段必须等待 T-103 真机取证;不展示地址、手机号或付款信息。</div></div></section>
|
||||
<section class="drawer-panel"><h3>执行证据</h3><div class="evidence-placeholder"><div><strong>内部截图证据占位</strong><br>真实字段等待真机取证;完整 XML 与支付凭据不会进入采购服务。</div></div></section>
|
||||
</div>
|
||||
<footer class="drawer-actions"><button class="button button-secondary" id="drawer-close-secondary" type="button">关闭</button><a class="button button-primary" id="full-detail-link" href="web-task-detail.html">在完整页面打开</a></footer>
|
||||
</dialog>
|
||||
@@ -278,7 +278,7 @@
|
||||
'use strict';
|
||||
const tasks = [
|
||||
{ id:'T-20260803-023', title:'仓库雨衣补货', productUrl:'https://mobile.yangkeduo.invalid/goods.html?goods_id=900000023', color:'荧光黄', size:'XXL', maxPrice:'96.00', quantity:3, status:'DRAFT', createdAt:'2026-08-03T14:42:00+08:00', result:null },
|
||||
{ id:'T-20260803-022', title:'8 月仓库工作服补货', productUrl:'https://mobile.yangkeduo.invalid/goods.html?goods_id=900000022', color:'白色', size:'XL', maxPrice:'80.00', quantity:2, status:'WAITING_CONFIRMATION', createdAt:'2026-08-03T13:18:00+08:00', result:{ phase:'试选', color:'白色', size:'XL', unitPrice:'32.50', quantity:2 } },
|
||||
{ id:'T-20260803-022', title:'8 月仓库工作服补货', productUrl:'https://mobile.yangkeduo.invalid/goods.html?goods_id=900000022', color:'白色', size:'XL', maxPrice:'80.00', quantity:2, status:'ORDERING', createdAt:'2026-08-03T13:18:00+08:00', result:{ phase:'采购执行', color:'白色', size:'XL', unitPrice:'32.50', quantity:2 } },
|
||||
{ id:'T-20260803-021', title:'办公室收纳盒', productUrl:'https://mobile.yangkeduo.invalid/goods.html?goods_id=900000021', color:'透明', size:'L', maxPrice:'60.00', quantity:2, status:'RECONCILIATION_REQUIRED', createdAt:'2026-08-03T11:36:00+08:00', result:{ phase:'下单待核查', color:'透明', size:'L', unitPrice:'24.00', quantity:2 } },
|
||||
{ id:'T-20260803-020', title:'仓库防滑手套', productUrl:'https://mobile.yangkeduo.invalid/goods.html?goods_id=900000020', color:'黑色', size:'均码', maxPrice:'64.00', quantity:5, status:'PENDING', createdAt:'2026-08-03T10:45:00+08:00', result:null },
|
||||
{ id:'T-20260801-009', title:'标签打印纸补货', productUrl:'https://mobile.yangkeduo.invalid/goods.html?goods_id=900000009', color:'白色', size:'50×30', maxPrice:'50.00', quantity:1, status:'WAITING_PAYMENT', createdAt:'2026-08-01T16:12:00+08:00', result:{ phase:'已下单', color:'白色', size:'50×30', unitPrice:'46.00', quantity:1 } },
|
||||
@@ -286,9 +286,10 @@
|
||||
];
|
||||
|
||||
const statusMeta = {
|
||||
DRAFT:{ label:'待开始', tone:'neutral', next:'勾选后点击“开始试选”,任务才会进入设备领取队列。' },
|
||||
PENDING:{ label:'待领取', tone:'', next:'已进入第一趟试选队列,等待桌面端轮询领取。' },
|
||||
WAITING_CONFIRMATION:{ label:'等待确认', tone:'', next:'查看规格面板证据,确认机器选对后才可签发下单授权。' },
|
||||
DRAFT:{ label:'待开始', tone:'neutral', next:'勾选后点击“开始采购(只创建待付款订单)”,才会签发一次性授权。' },
|
||||
PENDING:{ label:'已授权待领取', tone:'', next:'管理员已授权创建待付款订单,等待采购工具领取;系统不会付款。' },
|
||||
ORDERING:{ label:'采购执行中', tone:'', next:'采购工具正在同一趟校验规格、价格和数量;无需中途确认。' },
|
||||
NEEDS_MANUAL:{ label:'待人工处理', tone:'warning', next:'围栏前执行已停止。核查原因后可重置为待开始或取消。' },
|
||||
RECONCILIATION_REQUIRED:{ label:'结果待核查', tone:'warning', next:'订单可能已创建。只核查同一提交记录,不得重新提交。' },
|
||||
WAITING_PAYMENT:{ label:'待付款', tone:'warning', next:'人在拼多多核对商品、规格、数量和金额后手工付款;系统不会付款。' },
|
||||
SUCCEEDED:{ label:'已完成', tone:'success', next:'已由人工付款并标记完成,可查看执行证据与审计记录。' }
|
||||
@@ -303,6 +304,7 @@
|
||||
const filterForm = document.querySelector('#filter-form');
|
||||
const selectionMode = document.querySelector('#selection-mode');
|
||||
const selectionCount = document.querySelector('#selection-count');
|
||||
const selectionCap = document.querySelector('#selection-cap');
|
||||
const selectAll = document.querySelector('#select-all');
|
||||
const states = { loading:document.querySelector('#loading-state'), empty:document.querySelector('#empty-state'), error:document.querySelector('#error-state') };
|
||||
const selectedIds = new Set();
|
||||
@@ -325,6 +327,12 @@
|
||||
return `${whole}.${fraction}`;
|
||||
}
|
||||
|
||||
function moneyToCents(value) {
|
||||
const normalized = normalizeMoney(value);
|
||||
const [whole, fraction] = normalized.split('.');
|
||||
return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction, 10);
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
return new Intl.DateTimeFormat('zh-CN',{ year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit', hour12:false }).format(new Date(value));
|
||||
}
|
||||
@@ -375,6 +383,8 @@
|
||||
filterForm.hidden = count > 0;
|
||||
selectionMode.hidden = count === 0;
|
||||
selectionCount.textContent = `已选择 ${count} 条待开始任务`;
|
||||
const capInCents = tasks.filter((task) => selectedIds.has(task.id)).reduce((sum, task) => sum + moneyToCents(task.maxPrice), 0);
|
||||
selectionCap.textContent = `最高总额合计 ¥${(capInCents / 100).toFixed(2)}`;
|
||||
const selectableVisible = visibleTasks().filter((task) => task.status === 'DRAFT');
|
||||
const selectedVisible = selectableVisible.filter((task) => selectedIds.has(task.id));
|
||||
selectAll.checked = selectableVisible.length > 0 && selectedVisible.length === selectableVisible.length;
|
||||
@@ -438,12 +448,12 @@
|
||||
});
|
||||
|
||||
document.querySelector('#clear-selection').addEventListener('click', () => { selectedIds.clear(); renderRows(); keyword.focus(); });
|
||||
document.querySelector('#start-trial').addEventListener('click', () => {
|
||||
document.querySelector('#start-purchase').addEventListener('click', () => {
|
||||
const ids = [...selectedIds];
|
||||
tasks.forEach((task) => { if (selectedIds.has(task.id) && task.status === 'DRAFT') task.status = 'PENDING'; });
|
||||
selectedIds.clear();
|
||||
renderRows();
|
||||
showToast(`已将 ${ids.length} 条任务加入第一趟试选队列。没有签发下单授权,也不会付款。`);
|
||||
showToast(`已授权 ${ids.length} 条任务创建待付款订单。采购工具将逐条执行,系统不会付款。`);
|
||||
});
|
||||
|
||||
function isInteractive(target) { return Boolean(target.closest('a,button,input,label,select,textarea,summary')); }
|
||||
@@ -548,7 +558,7 @@
|
||||
drawerId.textContent = `${task.id} · ${meta.label}`;
|
||||
drawerTitle.textContent = task.title;
|
||||
drawerRequirements.innerHTML = `<div><dt>颜色</dt><dd>${escapeHtml(task.color)}</dd></div><div><dt>尺码</dt><dd>${escapeHtml(task.size)}</dd></div><div><dt>价格上限</dt><dd>¥${escapeHtml(task.maxPrice)}</dd></div><div><dt>数量</dt><dd>${task.quantity} 件</dd></div><div><dt>状态</dt><dd><span class="status ${meta.tone}">${escapeHtml(meta.label)}</span></dd></div><div><dt>创建时间</dt><dd>${escapeHtml(formatDate(task.createdAt))}</dd></div>`;
|
||||
drawerResult.innerHTML = task.result ? `<p style="margin:0 0 10px">${formatResult(task)}</p><p style="margin:0;color:var(--muted);font-size:.82rem">展示值来自结构化颜色、尺码、单价和数量;“|”只用于页面格式化。</p>` : '<p class="result empty" style="margin:0">尚无采购结果。任务未完成试选或下单,不用其他数据填充。</p>';
|
||||
drawerResult.innerHTML = task.result ? `<p style="margin:0 0 10px">${formatResult(task)}</p><p style="margin:0;color:var(--muted);font-size:.82rem">展示值来自结构化颜色、尺码、单价和数量;“|”只用于页面格式化。</p>` : '<p class="result empty" style="margin:0">尚无采购结果。任务未开始或仍在执行,不用其他数据填充。</p>';
|
||||
drawerNext.textContent = meta.next;
|
||||
fullDetailLink.href = `web-task-detail.html#${encodeURIComponent(task.id)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user