Files
cmbuyer/docs/design/web-task-detail.html
T

336 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>任务详情 · 采购服务原型</title>
<style>
:root {
--bg:#f4f7fb; --surface:#fff; --subtle:#f8fafc; --text:#172033; --muted:#526079; --border:#cfd8e6;
--primary:#155eef; --primary-hover:#0b4ed1; --primary-soft:#eaf1ff; --danger:#b42318; --danger-hover:#8f1d14;
--danger-soft:#fef3f2; --success:#067647; --success-soft:#ecfdf3; --warning:#8a4b0f; --warning-soft:#fff7e8;
--focus:#ffbf47; --shadow:0 6px 20px rgba(23,32,51,.08);
font-family:"Segoe UI","Microsoft YaHei UI",system-ui,sans-serif;
}
* { box-sizing:border-box; }
html { min-width:320px; background:var(--bg); }
body { margin:0; min-height:100dvh; color:var(--text); background:var(--bg); font-size:16px; line-height:1.5; }
a { color:var(--primary); text-underline-offset:3px; }
button,input,select,textarea { font:inherit; }
button,.button,input,select { min-height:44px; }
button { cursor:pointer; }
button:disabled { cursor:not-allowed; opacity:.56; }
:focus-visible { outline:3px solid var(--focus); outline-offset:3px; }
.skip-link { position:fixed; z-index:1000; top:8px; left:8px; padding:10px 14px; color:#fff; background:var(--text); transform:translateY(-160%); }
.skip-link:focus { transform:translateY(0); }
.prototype-banner { position:sticky; z-index:50; top:0; padding:7px 16px; color:#fff; background:#29364d; text-align:center; font-size:.78rem; font-weight:700; letter-spacing:.04em; }
.app-header { position:sticky; z-index:40; top:33px; min-height:64px; display:flex; align-items:center; gap:24px; padding:0 max(16px,calc((100vw - 1280px)/2)); border-bottom:1px solid var(--border); background:rgba(255,255,255,.98); }
.brand { display:flex; align-items:center; gap:10px; color:var(--text); text-decoration:none; font-weight:750; white-space:nowrap; }
.brand-mark { display:grid; place-items:center; width:32px; height:32px; border-radius:8px; color:#fff; background:var(--primary); font-size:.8rem; }
nav.primary { display:flex; align-items:stretch; align-self:stretch; gap:4px; }
nav.primary a { display:flex; align-items:center; padding:0 12px; color:#3d4b63; text-decoration:none; font-weight:650; border-bottom:3px solid transparent; }
nav.primary a[aria-current="page"] { color:var(--primary); border-bottom-color:var(--primary); }
.header-account { margin-left:auto; color:var(--muted); font-size:.88rem; white-space:nowrap; }
main { width:min(100% - 32px,1200px); margin:0 auto; padding:24px 0 64px; }
.breadcrumb { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:11px; color:var(--muted); font-size:.86rem; }
.page-head { display:flex; align-items:start; justify-content:space-between; gap:20px; margin-bottom:16px; }
h1 { margin:0; font-size:clamp(1.5rem,3vw,1.95rem); line-height:1.25; }
.task-id { margin:5px 0 0; color:var(--muted); font-size:.87rem; font-variant-numeric:tabular-nums; }
.state-chip { display:inline-flex; align-items:center; gap:7px; padding:7px 10px; border:1px solid #b9cffc; border-radius:999px; color:#163c8c; background:var(--primary-soft); font-size:.82rem; font-weight:750; }
.state-chip::before { content:""; width:8px; height:8px; border-radius:50%; background:currentColor; }
.safety-bar { display:grid; grid-template-columns:auto 1fr; gap:10px; align-items:start; margin-bottom:16px; padding:13px 15px; border:1px solid #b9cffc; border-radius:10px; background:var(--primary-soft); color:#29466f; }
.safety-bar strong { color:#163c8c; }
.layout { display:grid; grid-template-columns:minmax(0,1fr) 300px; gap:18px; align-items:start; }
.panel { border:1px solid var(--border); border-radius:12px; background:var(--surface); box-shadow:var(--shadow); }
.main-panel { min-width:0; }
.panel-head { display:flex; align-items:start; justify-content:space-between; gap:12px; padding:18px 20px 14px; border-bottom:1px solid var(--border); }
.panel-head h2 { margin:0; font-size:1.18rem; }
.panel-head p { margin:5px 0 0; color:var(--muted); font-size:.88rem; }
.panel-body { padding:20px; }
.state-panel[hidden] { display:none; }
.notice { margin:0 0 16px; padding:12px 14px; border-left:4px solid var(--primary); border-radius:6px; color:#29466f; background:var(--primary-soft); }
.notice.warning { border-left-color:#b7751b; color:var(--warning); background:var(--warning-soft); }
.notice.danger { border-left-color:var(--danger); color:var(--danger); background:var(--danger-soft); }
.notice.success { border-left-color:var(--success); color:var(--success); background:var(--success-soft); }
.notice strong { display:block; margin-bottom:2px; }
.comparison { width:100%; border-collapse:collapse; margin-bottom:16px; table-layout:fixed; }
.comparison th,.comparison td { padding:10px 9px; border-bottom:1px solid #e4e9f0; text-align:left; vertical-align:top; overflow-wrap:anywhere; }
.comparison th { color:var(--muted); background:var(--subtle); font-size:.8rem; }
.comparison td { font-size:.9rem; }
.result-text { font-weight:750; }
.result-text.good { color:var(--success); }
.result-text.bad { color:var(--danger); }
.evidence { margin:16px 0; border:1px solid var(--border); border-radius:10px; overflow:hidden; background:#eef2f7; }
.evidence svg { display:block; width:100%; height:auto; min-height:180px; }
.evidence figcaption { padding:9px 12px; color:var(--muted); background:var(--surface); font-size:.8rem; }
.summary-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; margin:0 0 18px; }
.summary-item { min-width:0; padding:11px 12px; border:1px solid #e0e6ee; border-radius:8px; background:var(--subtle); }
.summary-item dt { color:var(--muted); font-size:.76rem; }
.summary-item dd { margin:2px 0 0; overflow-wrap:anywhere; font-weight:650; font-variant-numeric:tabular-nums; }
.actions { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:10px; margin-top:18px; padding-top:16px; border-top:1px solid var(--border); }
.actions.between { justify-content:space-between; }
.button { display:inline-flex; align-items:center; justify-content:center; gap:8px; padding:9px 16px; border:1px solid transparent; border-radius:8px; text-decoration:none; font-weight:700; }
.button-primary { color:#fff; background:var(--primary); }
.button-primary:hover:not(:disabled) { background:var(--primary-hover); }
.button-secondary { color:var(--text); border-color:var(--border); background:var(--surface); }
.button-secondary:hover:not(:disabled) { background:var(--subtle); }
.button-danger { color:#fff; background:var(--danger); }
.button-danger:hover:not(:disabled) { background:var(--danger-hover); }
.button-danger-outline { color:var(--danger); border-color:#e0a29c; background:var(--surface); }
.button-danger-outline:hover:not(:disabled) { background:var(--danger-soft); }
.spinner { width:16px; height:16px; border:2px solid rgba(255,255,255,.45); border-top-color:#fff; border-radius:50%; animation:spin .8s linear infinite; }
.substate-control { display:flex; align-items:center; gap:10px; margin-bottom:14px; padding:10px 12px; border:1px dashed #aebacc; border-radius:8px; background:var(--subtle); }
.substate-control label { flex:0 0 auto; font-size:.82rem; font-weight:700; }
.substate-control select { width:100%; padding:8px 10px; border:1px solid #91a0b4; border-radius:7px; background:#fff; }
.side-panel { position:sticky; top:116px; }
.side-panel section { padding:15px; border-bottom:1px solid var(--border); }
.side-panel section:last-child { border-bottom:0; }
.side-panel h2 { margin:0 0 8px; font-size:.98rem; }
.side-panel p { margin:0; color:var(--muted); font-size:.84rem; }
.state-select { width:100%; margin-top:8px; padding:8px 10px; border:1px solid #91a0b4; border-radius:7px; background:#fff; }
.timeline { list-style:none; margin:10px 0 0; padding:0; }
.timeline li { position:relative; padding:0 0 13px 18px; color:#33415b; font-size:.82rem; }
.timeline li::before { content:""; position:absolute; left:0; top:.4em; width:8px; height:8px; border-radius:50%; background:#8ba0bd; }
.timeline li::after { content:""; position:absolute; left:3px; top:1em; bottom:1px; width:2px; background:#d8e0eb; }
.timeline li:last-child::after { display:none; }
.timeline strong { display:block; color:var(--text); }
.fence-lock { padding:14px; border:2px solid #e0bd75; border-radius:10px; background:var(--warning-soft); }
.fence-lock h3 { margin:0 0 5px; color:var(--warning); font-size:1rem; }
.fence-lock p { margin:4px 0; color:#674018; }
.mono { font-family:ui-monospace,"Cascadia Mono",Consolas,monospace; font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
.checklist { display:grid; gap:9px; margin:15px 0; }
.checklist label,.radio-list label { display:flex; gap:10px; align-items:flex-start; min-height:44px; padding:10px 12px; border:1px solid var(--border); border-radius:8px; background:var(--surface); }
.checklist input,.radio-list input { width:18px; height:18px; flex:0 0 auto; margin-top:2px; accent-color:var(--primary); }
.radio-list { display:grid; gap:8px; margin:12px 0; }
textarea { width:100%; min-height:84px; padding:10px; border:1px solid #91a0b4; border-radius:8px; resize:vertical; }
.field-label { display:block; margin:14px 0 6px; font-weight:700; }
.terminal-mark { display:grid; place-items:center; width:54px; height:54px; margin:0 auto 12px; border-radius:50%; color:var(--success); border:2px solid currentColor; font-weight:800; }
.terminal { text-align:center; }
.terminal p { max-width:600px; margin:0 auto 10px; color:var(--muted); }
.live-region { margin-bottom:14px; }
.live-region:empty { display:none; }
dialog { width:min(100% - 32px,500px); padding:0; border:0; border-radius:12px; color:var(--text); box-shadow:0 24px 80px rgba(0,0,0,.3); }
dialog::backdrop { background:rgba(15,23,42,.58); }
.dialog-head { padding:18px 20px 12px; border-bottom:1px solid var(--border); }
.dialog-head h2 { margin:0; font-size:1.15rem; }
.dialog-body { padding:18px 20px; }
.dialog-body p { margin:0 0 10px; }
.dialog-actions { display:flex; justify-content:flex-end; flex-wrap:wrap; gap:9px; padding:14px 20px 18px; }
@keyframes spin { to { transform:rotate(360deg); } }
@media (max-width:900px) { .layout{grid-template-columns:1fr}.side-panel{position:static;display:grid;grid-template-columns:1fr 1fr}.side-panel section{border-bottom:0}.side-panel section+section{border-left:1px solid var(--border)} }
@media (max-width:767px) {
.app-header{top:33px;padding-inline:16px;gap:10px}.header-account{display:none}nav.primary a{padding-inline:8px}main{width:min(100% - 24px,1200px);padding-top:18px}.page-head{flex-direction:column;gap:10px}.panel-head,.panel-body{padding-inline:15px}.summary-grid{grid-template-columns:1fr}.actions,.actions.between{flex-direction:column-reverse}.actions .button{width:100%}.substate-control{align-items:stretch;flex-direction:column}.side-panel{grid-template-columns:1fr}.side-panel section+section{border-left:0;border-top:1px solid var(--border)}
.comparison,.comparison tbody,.comparison tr,.comparison td{display:block;width:100%}.comparison thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.comparison tr{padding:8px 0;border-bottom:1px solid #e4e9f0}.comparison td{display:grid;grid-template-columns:110px 1fr;gap:8px;padding:5px 0;border:0}.comparison td::before{content:attr(data-label);color:var(--muted);font-size:.78rem;font-weight:700}
}
@media (max-width:430px) { .prototype-banner{position:static;padding-inline:8px}.app-header{top:0}.brand span:last-child,nav.primary a:last-child{display:none}.safety-bar{grid-template-columns:1fr}.comparison td{grid-template-columns:92px 1fr} }
@media (prefers-reduced-motion:reduce) { *,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important} }
</style>
</head>
<body>
<a class="skip-link" href="#main">跳到主要内容</a>
<div class="prototype-banner">PROTOTYPE - 仅供枚举交互,非实现依据</div>
<header class="app-header">
<a class="brand" href="web-task-workbench.html" aria-label="采购服务原型工作台"><span class="brand-mark" aria-hidden="true">采</span><span>采购服务</span></a>
<nav class="primary" aria-label="主导航"><a href="web-task-workbench.html" aria-current="page">任务工作台</a><a href="web-task-create.html">新建任务</a></nav>
<span class="header-account">采购管理员 · caigou_admin</span>
</header>
<main id="main">
<nav class="breadcrumb" aria-label="面包屑"><a href="web-task-workbench.html">任务工作台</a><span aria-hidden="true">/</span><span>任务详情</span></nav>
<header class="page-head">
<div><h1>8 月仓库工作服补货</h1><p class="task-id">任务 T-20260803-018 · 假数据</p></div>
<span class="state-chip" id="state-chip">等待确认</span>
</header>
<div class="safety-bar"><strong>只下单,不付款</strong><span>系统最多创建待付款订单。付款始终由人在拼多多核对后完成;待付款状态不是成功。</span></div>
<div class="layout">
<section class="panel main-panel" aria-label="当前任务状态">
<div class="live-region" id="live-region" aria-live="polite"></div>
<article class="state-panel" data-state-panel="waiting">
<header class="panel-head"><div><h2>机器选对了吗?</h2><p>确认前必须查看实际勾选结果、价格和规格面板证据。</p></div></header>
<div class="panel-body">
<div class="substate-control"><label for="trial-variant">原型子状态</label><select id="trial-variant"><option value="match">规格一致、金额未超</option><option value="mismatch">规格不一致</option><option value="over">金额超上限</option><option value="missing">截图缺失</option></select></div>
<div class="notice" id="trial-notice"><strong>可以授权</strong>机器所选规格与要求一致,合计未超过资金上限。</div>
<table class="comparison">
<thead><tr><th scope="col">核对项</th><th scope="col">你要的</th><th scope="col">机器选到</th><th scope="col">结论</th></tr></thead>
<tbody>
<tr><td data-label="核对项">颜色分类</td><td data-label="你要的">白色</td><td data-label="机器选到" id="actual-color">白色</td><td data-label="结论"><span class="result-text good" id="color-result">符合</span></td></tr>
<tr><td data-label="核对项">尺码</td><td data-label="你要的">XL</td><td data-label="机器选到">XL</td><td data-label="结论"><span class="result-text good">符合</span></td></tr>
<tr><td data-label="核对项">金额</td><td data-label="你要的">总额不超过 ¥80.00</td><td data-label="机器选到" id="actual-price">¥32.50 × 2 = ¥65.00</td><td data-label="结论"><span class="result-text good" id="price-result">未超上限</span></td></tr>
</tbody>
</table>
<figure class="evidence" id="trial-evidence">
<svg viewBox="0 0 760 300" role="img" aria-labelledby="trial-svg-title trial-svg-desc">
<title id="trial-svg-title">规格面板截图假数据占位</title><desc id="trial-svg-desc">用于确认信息架构,不代表真实拼多多页面结构或字段位置。</desc>
<rect width="760" height="300" fill="#e7edf5"/><rect x="30" y="26" width="700" height="248" rx="12" fill="#fff" stroke="#b8c4d5"/>
<text x="55" y="62" fill="#526079" font-size="18">假数据证据占位 · 不代表真实 App 页面</text><line x1="55" y1="78" x2="705" y2="78" stroke="#d6dde8"/>
<rect x="55" y="102" width="190" height="42" rx="7" fill="#eaf1ff" stroke="#8aacf2"/><text x="72" y="129" fill="#163c8c" font-size="16">颜色分类:白色(假)</text>
<rect x="265" y="102" width="150" height="42" rx="7" fill="#eaf1ff" stroke="#8aacf2"/><text x="282" y="129" fill="#163c8c" font-size="16">尺码:XL(假)</text>
<rect x="55" y="166" width="360" height="58" rx="7" fill="#f8fafc" stroke="#cfd8e6"/><text x="72" y="201" fill="#172033" font-size="22">规格面板读价:¥32.50(假)</text>
<text x="55" y="252" fill="#526079" font-size="15">真实可读字段需等待 T-103 真机取证后复核</text>
</svg>
<figcaption>规格面板证据 · 假数据 · 未包含地址、手机号或付款信息</figcaption>
</figure>
<label class="field-label" for="auth-note">授权备注(可选)</label><textarea id="auth-note" maxlength="500" placeholder="只记录本次决定所需的非敏感信息"></textarea>
<div class="actions between"><button class="button button-danger-outline" id="reject-button" type="button">退回,不买</button><button class="button button-primary" id="authorize-button" type="button">确认下单(不付款)</button></div>
</div>
</article>
<article class="state-panel" data-state-panel="authorized" hidden>
<header class="panel-head"><div><h2>授权已签发,等待第二趟</h2><p>尚未建立提交围栏;旧授权仍可安全放弃,但必须重新试选取价。</p></div></header>
<div class="panel-body">
<div class="notice"><strong>系统正在等待桌面端领取</strong>当前没有创建订单,也没有发生付款。</div>
<dl class="summary-grid"><div class="summary-item"><dt>锁定单价</dt><dd>¥32.50</dd></div><div class="summary-item"><dt>授权总额上限</dt><dd>¥80.00</dd></div><div class="summary-item"><dt>授权编号</dt><dd class="mono">AUTH-018-V5</dd></div><div class="summary-item"><dt>围栏状态</dt><dd>尚未建立</dd></div></dl>
<div class="notice warning"><strong>放弃不会复用旧价</strong>任务将转为待重新试选,完成新试选后才能再次确认。</div>
<div class="actions"><button class="button button-danger" type="button" data-open-abandon>放弃授权并重新试选</button></div>
</div>
</article>
<article class="state-panel" data-state-panel="ordering-pre" hidden>
<header class="panel-head"><div><h2>第二趟执行中 · 围栏前</h2><p>桌面端正在重新选同一规格并复核价格,尚未取得提交许可。</p></div></header>
<div class="panel-body">
<ol class="timeline"><li><strong>已重新打开商品</strong>页面细节不在本原型定义</li><li><strong>正在重新选择规格</strong>必须按维度精确匹配</li><li><strong>待价格闸门复核</strong>现价必须等于锁定价 ¥32.50</li><li><strong>尚未申请提交围栏</strong>不会点击提交订单</li></ol>
<div class="notice warning"><strong>围栏前仍可安全放弃</strong>放弃后不会沿用旧试选价格,任务回到待重新试选。</div>
<div class="actions"><button class="button button-danger" type="button" data-open-abandon>放弃授权并重新试选</button></div>
</div>
</article>
<article class="state-panel" data-state-panel="retrial" hidden>
<header class="panel-head"><div><h2>等待重新试选</h2><p>旧授权已作废,旧价格不再有效。</p></div></header>
<div class="panel-body terminal"><div class="terminal-mark" aria-hidden="true">新</div><div class="notice"><strong>下一步安全可执行</strong>桌面端会重新跑第一趟,读取新价格并回传新证据;在此之前页面不会显示确认入口。</div><p>当前没有有效授权,没有建立提交围栏,也不会创建订单。</p><div class="actions"><a class="button button-primary" href="web-task-workbench.html">返回任务工作台</a></div></div>
</article>
<article class="state-panel" data-state-panel="fenced" hidden>
<header class="panel-head"><div><h2>提交围栏已建立</h2><p>只允许恢复和核查这一笔提交记录。</p></div></header>
<div class="panel-body">
<div class="fence-lock"><h3>禁止再次提交或放弃</h3><p>唯一提交记录:<strong class="mono">SUB-018-5A7C</strong></p><p>围栏建立后即使连接中断,也不能释放授权、重新领取或再点一次。</p></div>
<dl class="summary-grid" style="margin-top:16px"><div class="summary-item"><dt>提交记录</dt><dd class="mono">SUB-018-5A7C</dd></div><div class="summary-item"><dt>授权状态</dt><dd>已围栏</dd></div><div class="summary-item"><dt>确认页金额</dt><dd>¥65.00</dd></div><div class="summary-item"><dt>付款状态</dt><dd>未付款</dd></div></dl>
<div class="notice warning"><strong>这里没有重试或放弃入口</strong>如果结果不明,只能核查同一提交编号,避免产生第二笔订单。</div>
<div class="actions"><button class="button button-primary" type="button" data-go-reconcile>进入人工核查</button></div>
</div>
</article>
<article class="state-panel" data-state-panel="reconcile" hidden>
<header class="panel-head"><div><h2>订单结果需要人工核查</h2><p>订单可能已创建,请勿再次提交;授权额度继续预留。</p></div></header>
<div class="panel-body">
<div class="notice warning"><strong>结果不明,不代表失败</strong>提交记录 <span class="mono">SUB-018-5A7C</span> 已被永久围栏。当前只记录核查结果,不发起新的下单。</div>
<dl class="summary-grid"><div class="summary-item"><dt>观察结果</dt><dd>页面超时,无法确认</dd></div><div class="summary-item"><dt>付款状态</dt><dd>未知;系统未付款</dd></div><div class="summary-item"><dt>证据</dt><dd>1 张脱敏截图(假)</dd></div><div class="summary-item"><dt>可用动作</dt><dd>仅人工核查</dd></div></dl>
<fieldset style="border:0;padding:0;margin:0"><legend style="font-weight:750">人工核查结论</legend><div class="radio-list"><label><input type="radio" name="review" value="created">已确认创建了待付款订单</label><label><input type="radio" name="review" value="not-created">已确认没有创建订单</label><label><input type="radio" name="review" value="unknown" checked>仍不明确,继续保留围栏</label></div></fieldset>
<label class="field-label" for="review-note">核查说明(必填,勿写敏感信息)</label><textarea id="review-note" placeholder="例如:已由人工查看拼多多待付款列表;本原型不访问真实页面">仍需人工查看待付款列表(假数据)</textarea>
<div class="actions"><button class="button button-primary" id="save-review" type="button">保存核查记录</button></div>
</div>
</article>
<article class="state-panel" data-state-panel="payment" hidden>
<header class="panel-head"><div><h2>待付款核对</h2><p>订单已创建,但任务尚未成功。请先在拼多多逐项核对。</p></div></header>
<div class="panel-body">
<div class="notice warning"><strong>对不上就别付</strong>不一致时回到这里标记异常,不要在拼多多改单。系统不会代付。</div>
<figure class="evidence"><svg viewBox="0 0 760 260" role="img" aria-labelledby="order-svg-title order-svg-desc"><title id="order-svg-title">订单截图假数据占位</title><desc id="order-svg-desc">仅用于原型核对流程,不表示真实拼多多订单页布局。</desc><rect width="760" height="260" fill="#e7edf5"/><rect x="30" y="25" width="700" height="210" rx="12" fill="#fff" stroke="#b8c4d5"/><text x="54" y="60" fill="#526079" font-size="18">待付款订单证据 · 假数据</text><line x1="54" y1="76" x2="706" y2="76" stroke="#d6dde8"/><text x="54" y="112" fill="#172033" font-size="18">商品:2026 夏季纯棉圆领短袖(假)</text><text x="54" y="150" fill="#172033" font-size="18">规格:白色 · XL 数量:2</text><text x="54" y="194" fill="#172033" font-size="22">授权金额:¥65.00 状态:待付款</text></svg><figcaption>订单证据 · 假数据 · 真实字段需 T-103 后复核</figcaption></figure>
<div class="checklist"><label><input type="checkbox" data-payment-check>拼多多中商品与这里展示的一致</label><label><input type="checkbox" data-payment-check>规格、数量与这里展示的一致</label><label><input type="checkbox" data-payment-check>付款金额不超过授权金额 ¥80.00</label><label><input type="checkbox" data-payment-check>我已由人工在拼多多完成付款</label></div>
<div class="actions between"><button class="button button-danger-outline" id="mark-abnormal" type="button">标记异常,先不付款</button><button class="button button-primary" id="mark-paid" type="button" disabled aria-describedby="paid-disabled-reason">确认已付款并完成</button></div>
<p id="paid-disabled-reason" style="margin:8px 0 0;color:var(--muted);font-size:.82rem">完成全部四项人工核对后才能标记。</p>
</div>
</article>
<article class="state-panel" data-state-panel="manual" hidden>
<header class="panel-head"><div><h2>需要人工处理</h2><p>这是围栏前异常;系统已经停止,不会猜测或继续执行。</p></div></header>
<div class="panel-body"><div class="notice danger"><strong>价格与授权不一致</strong>授权单价 ¥32.50,第二趟读到 ¥35.00。系统没有申请提交围栏,也没有点击提交订单。</div><dl class="summary-grid"><div class="summary-item"><dt>失败阶段</dt><dd>第二趟 · 价格闸门二</dd></div><div class="summary-item"><dt>安全处置</dt><dd>停止并保留证据</dd></div><div class="summary-item"><dt>可做什么</dt><dd>核查原因,决定重新试选</dd></div><div class="summary-item"><dt>不可做什么</dt><dd>按新价继续下单</dd></div></dl><label class="field-label" for="manual-note">处理记录</label><textarea id="manual-note" placeholder="记录下一步,不填写敏感信息"></textarea><div class="actions"><button class="button button-primary" id="record-manual" type="button">记录人工处理结果</button></div></div>
</article>
<article class="state-panel" data-state-panel="succeeded" hidden><header class="panel-head"><div><h2>任务已完成</h2><p>人工付款并核对后才进入此状态。</p></div></header><div class="panel-body terminal"><div class="terminal-mark" aria-hidden="true">完成</div><p><strong>已由采购管理员于 2026-08-03 11:20 标记完成。</strong></p><p>付款由人在拼多多完成,系统未读取或保存任何付款凭据。</p><div class="actions"><a class="button button-primary" href="web-task-workbench.html">返回任务工作台</a></div></div></article>
<article class="state-panel" data-state-panel="failed" hidden><header class="panel-head"><div><h2>任务失败</h2><p>失败原因可区分,并保留当时证据。</p></div></header><div class="panel-body"><div class="notice danger"><strong>设备连接中断</strong>发生在第一趟打开商品前,没有进行规格选择或下单动作。</div><dl class="summary-grid"><div class="summary-item"><dt>原因码</dt><dd class="mono">DEVICE_DISCONNECTED</dd></div><div class="summary-item"><dt>下一步</dt><dd>检查设备连接后,由人工决定重新试选或结束任务</dd></div></dl><div class="actions"><a class="button button-primary" href="web-task-workbench.html">返回工作台</a></div></div></article>
<article class="state-panel" data-state-panel="canceled" hidden><header class="panel-head"><div><h2>任务已取消</h2><p>管理员选择“退回,不买”,不会再自动执行。</p></div></header><div class="panel-body terminal"><div class="terminal-mark" style="color:var(--danger)" aria-hidden="true">停</div><p>没有签发下单授权,没有创建订单,也没有发生付款。</p><div class="actions"><a class="button button-primary" href="web-task-workbench.html">返回任务工作台</a></div></div></article>
</section>
<aside class="panel side-panel" aria-label="原型状态与任务摘要">
<section><h2>原型状态切换</h2><p>演示任务在不同业务状态下的唯一主区块,不发送请求。</p><label for="state-select" style="display:block;margin-top:10px;font-weight:700;font-size:.82rem">业务状态</label><select class="state-select" id="state-select"><option value="waiting">WAITING_CONFIRMATION</option><option value="authorized">AUTHORIZED</option><option value="ordering-pre">ORDERING · 围栏前</option><option value="retrial">PENDING_RETRIAL</option><option value="fenced">ORDERING · 已围栏</option><option value="reconcile">RECONCILIATION_REQUIRED</option><option value="payment">WAITING_PAYMENT</option><option value="manual">NEEDS_MANUAL</option><option value="succeeded">SUCCEEDED</option><option value="failed">FAILED</option><option value="canceled">CANCELED</option></select></section>
<section><h2>任务要求</h2><dl class="summary-grid" style="grid-template-columns:1fr;margin:0"><div class="summary-item"><dt>颜色分类 / 尺码</dt><dd>白色 / XL</dd></div><div class="summary-item"><dt>数量</dt><dd>2 件</dd></div><div class="summary-item"><dt>价格上限</dt><dd>¥80.00</dd></div></dl></section>
<section><h2>固定安全边界</h2><p>金额均为十进制字符串假数据。原型不定义拼多多页面判据,不调用接口,不读取地址、手机号或付款凭据。</p></section>
</aside>
</div>
</main>
<dialog id="reject-dialog" aria-labelledby="reject-title"><div class="dialog-head"><h2 id="reject-title">确认退回,不买?</h2></div><div class="dialog-body"><p>任务将结束为已取消,不会签发下单授权,也不会再自动执行。</p></div><div class="dialog-actions"><button class="button button-secondary" type="button" data-close-dialog>继续检查</button><button class="button button-danger" type="button" id="confirm-reject">确认退回</button></div></dialog>
<dialog id="abandon-dialog" aria-labelledby="abandon-title"><div class="dialog-head"><h2 id="abandon-title">放弃当前授权?</h2></div><div class="dialog-body"><p>当前确认尚未建立提交围栏,可以安全放弃。</p><p><strong>放弃后必须重新跑第一趟取新价,不能复用旧授权或旧截图。</strong></p></div><div class="dialog-actions"><button class="button button-secondary" type="button" data-close-dialog>保留授权</button><button class="button button-danger" type="button" id="confirm-abandon">放弃并重新试选</button></div></dialog>
<script>
(() => {
'use strict';
const select = document.querySelector('#state-select');
const chip = document.querySelector('#state-chip');
const live = document.querySelector('#live-region');
const labels = { waiting:'等待确认', authorized:'已授权 · 围栏前', 'ordering-pre':'第二趟 · 围栏前', retrial:'等待重新试选', fenced:'提交围栏已建立', reconcile:'结果需要调和', payment:'待付款', manual:'需要人工', succeeded:'已完成', failed:'失败', canceled:'已取消' };
const panels = [...document.querySelectorAll('[data-state-panel]')];
const rejectDialog = document.querySelector('#reject-dialog');
const abandonDialog = document.querySelector('#abandon-dialog');
function showState(state, message = '', shouldScroll = true) {
panels.forEach((panel) => { panel.hidden = panel.dataset.statePanel !== state; });
select.value = state;
chip.textContent = labels[state];
live.className = message ? 'notice success live-region' : 'live-region';
live.textContent = message;
if (shouldScroll) {
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.querySelector('.main-panel').scrollIntoView({ block:'start', behavior:reduceMotion ? 'auto' : 'smooth' });
}
}
select.addEventListener('change', () => showState(select.value));
const variant = document.querySelector('#trial-variant');
const actualColor = document.querySelector('#actual-color');
const colorResult = document.querySelector('#color-result');
const actualPrice = document.querySelector('#actual-price');
const priceResult = document.querySelector('#price-result');
const trialNotice = document.querySelector('#trial-notice');
const evidence = document.querySelector('#trial-evidence');
const authorizeButton = document.querySelector('#authorize-button');
function setTrialVariant(value) {
actualColor.textContent = value === 'mismatch' ? '粉白色' : '白色';
colorResult.textContent = value === 'mismatch' ? '不符合:颜色不同' : '符合';
colorResult.className = `result-text ${value === 'mismatch' ? 'bad' : 'good'}`;
actualPrice.textContent = value === 'over' ? '¥43.50 × 2 = ¥87.00' : '¥32.50 × 2 = ¥65.00';
priceResult.textContent = value === 'over' ? '超出 ¥7.00' : '未超上限';
priceResult.className = `result-text ${value === 'over' ? 'bad' : 'good'}`;
evidence.hidden = value === 'missing';
authorizeButton.disabled = value !== 'match';
if (value === 'match') { trialNotice.className='notice'; trialNotice.innerHTML='<strong>可以授权</strong>机器所选规格与要求一致,合计未超过资金上限。'; }
else if (value === 'mismatch') { trialNotice.className='notice danger'; trialNotice.innerHTML='<strong>不能授权:规格不一致</strong>颜色分类不相同,只能退回或转人工;不提供“仍然确认”。'; }
else if (value === 'over') { trialNotice.className='notice danger'; trialNotice.innerHTML='<strong>不能授权:金额超上限</strong>合计超出 ¥7.00,不提供绕过资金边界的入口。'; }
else { trialNotice.className='notice danger'; trialNotice.innerHTML='<strong>不能授权:证据缺失</strong>未加载出规格面板截图,无证据不能签发授权。'; }
}
variant.addEventListener('change', () => setTrialVariant(variant.value));
authorizeButton.addEventListener('click', () => {
authorizeButton.disabled = true;
authorizeButton.innerHTML = '<span class="spinner" aria-hidden="true"></span>正在签发授权…';
window.setTimeout(() => { authorizeButton.innerHTML='确认下单(不付款)'; authorizeButton.disabled=false; showState('authorized','授权已签发并锁定单价 ¥32.50。这里只允许创建待付款订单,系统不会付款。'); }, 500);
});
document.querySelector('#reject-button').addEventListener('click', () => rejectDialog.showModal());
document.querySelectorAll('[data-open-abandon]').forEach((button) => button.addEventListener('click', () => abandonDialog.showModal()));
document.querySelectorAll('[data-close-dialog]').forEach((button) => button.addEventListener('click', () => button.closest('dialog').close()));
document.querySelector('#confirm-reject').addEventListener('click', () => { rejectDialog.close(); showState('canceled','任务已退回并取消,不会再自动执行。'); });
document.querySelector('#confirm-abandon').addEventListener('click', () => { abandonDialog.close(); showState('retrial','旧授权已作废。任务必须先重新试选取新价,不能复用旧价格。'); });
document.querySelectorAll('[data-go-reconcile]').forEach((button) => button.addEventListener('click', () => showState('reconcile')));
document.querySelector('#save-review').addEventListener('click', () => { live.className='notice success live-region'; live.textContent='人工核查记录已保存为假数据。无论选择何种结论,本原型都不会发起新的下单。'; live.scrollIntoView({block:'nearest'}); });
const paymentChecks = [...document.querySelectorAll('[data-payment-check]')];
const markPaid = document.querySelector('#mark-paid');
function updatePaidButton() { markPaid.disabled = !paymentChecks.every((check) => check.checked); }
paymentChecks.forEach((check) => check.addEventListener('change', updatePaidButton));
markPaid.addEventListener('click', () => showState('succeeded','已记录人工付款与核对结果。系统没有执行付款。'));
document.querySelector('#mark-abnormal').addEventListener('click', () => showState('manual','已标记核对异常,任务不会被直接标记完成。'));
document.querySelector('#record-manual').addEventListener('click', () => { live.className='notice success live-region'; live.textContent='人工处理记录已保存为假数据。后续如需重来,仍必须重新试选取价。'; live.scrollIntoView({block:'nearest'}); });
[rejectDialog, abandonDialog].forEach((dialog) => dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); }));
setTrialVariant('match');
showState('waiting', '', false);
})();
</script>
</body>
</html>