feat(prototype): show record details inline
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@
|
||||
| T-003 | 建立 `init.ps1` 统一入口 | T-001, T-002 | 一条命令完成两端安装与基础验证并打印启动命令;未配置时主动失败而不是静默跳过 |
|
||||
| T-004 | 建立核心数据模型与状态机 | T-001 | `tasks`、`order_authorizations`、`order_submissions` 表与 `04-architecture.md` 一致;状态流转有单元测试;金额用十进制字符串 |
|
||||
| T-005 | 网页端 MVP 交互原型 | - | `docs/design/web-*.html` 单文件假数据原型覆盖登录、表格工作台、创建弹窗、批量开始试选、路由化详情抽屉与完整详情;键盘、窄屏、空态 / 错误 / 加载、围栏后调和状态可演示;经人工确认前保持 `DOING` |
|
||||
| T-006 | 桌面端 MVP 交互原型 | - | `docs/design/desk-*.html` 单文件假数据原型覆盖“采购工具”的采购执行 / 配置固定页签、顶部轮询控制、当前任务图片、滚动日志、时间倒序执行记录和详情弹窗;明确 dry-run / 真实下单、App 版本失配、围栏后不可重试;经人工确认前保持 `DOING` |
|
||||
| T-006 | 桌面端 MVP 交互原型 | - | `docs/design/desk-*.html` 单文件假数据原型覆盖“采购工具”的采购执行 / 配置固定页签、顶部轮询控制、当前任务图片、滚动日志、时间倒序执行记录,以及左侧当前任务 / 记录详情双视图;明确 dry-run / 真实下单、App 版本失配、围栏后不可重试;经人工确认前保持 `DOING` |
|
||||
|
||||
### Phase 1 · 真机取证(最高风险,必须先做)
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ IX 编号一经引用不再重用。交互清单不能扩大需求范围。
|
||||
|
||||
- 关联用户故事:US-003、US-008 | 关联需求:F-005、F-011
|
||||
- 页面 / 组件:desk 采购执行页 `PollControls` + `DeviceStatusBar` + `CurrentTaskPanel` +
|
||||
`ExecutionLog` + `ExecutionRecordTable` / `ExecutionRecordDialog`
|
||||
`ExecutionLog` + `ExecutionRecordTable` + `ExecutionRecordDetailPanel`
|
||||
- 目标角色:采购执行员
|
||||
- 前置条件:web 端连接正常;设备已通过连接检查
|
||||
|
||||
@@ -119,8 +119,8 @@ IX 编号一经引用不再重用。交互清单不能扩大需求范围。
|
||||
1. 用户确认 web、设备和已取证 App 版本三项状态都就绪,在顶部状态区点击「开始轮询」。
|
||||
2. 每到间隔时间领取一条任务,界面显示**这一趟是试选还是下单**、商品、规格、当前步骤。
|
||||
3. 第一趟试选完成后**退出商品页释放手机**,继续下一轮轮询。
|
||||
4. 执行结果按时间倒序进入右侧两列表格;单击只选中,双击、Enter 或可见查看命令打开记录
|
||||
详情,关闭后焦点回到原行。
|
||||
4. 执行结果按时间倒序进入右侧两列表格;单击只选中,双击、Enter 或可见查看命令在左侧打开
|
||||
记录详情。宽屏可继续选择其他记录;可见返回按钮或 `Esc` 回到当前任务并恢复当前行焦点。
|
||||
|
||||
**状态与异常清单**
|
||||
|
||||
@@ -130,7 +130,8 @@ IX 编号一经引用不再重用。交互清单不能扩大需求范围。
|
||||
| 轮询中 | 显示下次轮询倒计时与连续失败计数 |
|
||||
| 执行中 | 显示当前趟次与步骤;**真机步骤期间禁用硬取消和关闭窗口** |
|
||||
| 当前任务图片 | 无可信图片时显示空态;有图时标来源与采集时间,不用其他来源图片凑合 |
|
||||
| 执行记录详情 | 左侧原始文字、右侧图片证据、下方结构化采购结果;不提供下单、重试提交或付款动作 |
|
||||
| 执行记录详情 | 左栏在当前任务 / 记录详情间切换,不打开模态框;详情内左侧原始文字、右侧图片证据、下方结构化采购结果;顶部实时状态不中断;不提供下单、重试提交或付款动作 |
|
||||
| 详情读取异常 | 加载、失败、无图和记录不存在分别说明;仅可重试读取同一稳定记录 ID,过期异步结果不得覆盖当前视图 |
|
||||
| 无可领任务 | 显示「暂无待领任务」并继续下一轮,**不当作错误、不计入失败计数** |
|
||||
| 待人工 | 整页显著变色,说明缺什么、下一步做什么;该任务不再被本端领取 |
|
||||
| 连续失败 | 达到阈值自动停止轮询并显示原因,**不无限重试** |
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
- 标准启动路径:`./init.ps1`(Windows)/ `./init.sh`。已存在但会因两端目录未初始化而以
|
||||
退出码 3 结束并列出待办任务——这是预期行为
|
||||
- 标准验证路径:目前只有 `python scripts/validate_agent_context.py` 可跑通
|
||||
- 当前 blocker:T-005 第二轮 Web 表格工作台和 T-006 第二轮“采购工具”桌面工作区均已通过
|
||||
- 当前 blocker:T-005 第二轮 Web 表格工作台和 T-006 第三轮“采购工具”桌面工作区均已通过
|
||||
自动检查;两者经人工确认前不开始对应生产 UI。
|
||||
|
||||
## 当前目录要点
|
||||
@@ -43,11 +43,12 @@
|
||||
|
||||
- 已完成:无。
|
||||
- 正在进行:T-005(网页端 MVP 交互原型)、T-006(桌面端 MVP 交互原型)。T-005 第二轮
|
||||
已将三泳道改为传统表格;T-006 第二轮已改为“采购工具”的采购执行 / 配置固定页签,并增加
|
||||
顶部轮询控制、当前任务图片、滚动日志、执行记录表格和详情弹窗。两项自动验证均已完成,
|
||||
已将三泳道改为传统表格;T-006 第三轮已把执行记录详情从模态框改为左侧内联双视图,支持
|
||||
连续选行、可见返回按钮、`Esc` 层级、异常读取状态和紧凑宽度单视图。两项自动验证均已完成,
|
||||
都因 `needs_human_review: true` 保持 `DOING`。
|
||||
- **下一步:人工浏览 6 个原型,重点确认 Web 表格与详情抽屉,以及桌面端顶部状态区、三块
|
||||
布局、图片证据、两列执行记录和详情弹窗。** 明确确认后才能把 T-005 / T-006 改为 `DONE`。
|
||||
- **下一步:人工浏览 6 个原型,重点确认 Web 表格与详情抽屉,以及桌面端顶部状态区、图片
|
||||
证据、两列执行记录和左侧内联详情 / 返回路径。** 明确确认后才能把 T-005 / T-006 改为
|
||||
`DONE`。
|
||||
- 原型使用假数据、不调用真实接口、不驱动真机;T-001 / T-002 骨架可在确认后并行,
|
||||
Phase 2 生产页面还必须等待相关真机结论。
|
||||
|
||||
|
||||
+142
-52
@@ -168,7 +168,7 @@
|
||||
main { max-width: 1480px; margin: 0 auto; padding: 16px; }
|
||||
.prototype-tools {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 1fr) auto;
|
||||
grid-template-columns: minmax(240px, 1fr) minmax(180px, .55fr) auto;
|
||||
gap: 12px;
|
||||
align-items: end;
|
||||
margin-bottom: 12px;
|
||||
@@ -224,7 +224,9 @@
|
||||
.banner p { margin: 0; color: var(--text-primary); }
|
||||
.banner button[hidden] { display: none; }
|
||||
.workspace { display: grid; grid-template-columns: minmax(0, 1.85fr) minmax(320px, 1fr); gap: 12px; margin-top: 12px; align-items: stretch; }
|
||||
.left-pane { min-width: 0; min-height: 0; }
|
||||
.stack { display: grid; grid-template-rows: auto minmax(240px, 1fr); gap: 12px; min-width: 0; }
|
||||
.stack[hidden], .detail-workspace[hidden] { display: none; }
|
||||
.card { border: 1px solid var(--stroke); border-radius: var(--radius-card); background: var(--surface-raised); box-shadow: 0 2px 8px rgba(0,0,0,.04); overflow: hidden; }
|
||||
.card-header { display: flex; align-items: start; justify-content: space-between; gap: 12px; padding: 14px 16px 10px; border-bottom: 1px solid var(--divider); }
|
||||
.card-header h1, .card-header h2 { margin: 0; font-size: 17px; line-height: 1.35; }
|
||||
@@ -296,11 +298,17 @@
|
||||
.dialog-body p { margin: 0 0 10px; }
|
||||
.dialog-body code { overflow-wrap: anywhere; }
|
||||
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--divider); }
|
||||
.record-dialog { width: min(940px, calc(100vw - 32px)); }
|
||||
.record-dialog header { display: flex; justify-content: space-between; gap: 12px; padding: 16px 20px 12px; border-bottom: 1px solid var(--divider); }
|
||||
.record-dialog header h2 { margin: 0; font-size: 18px; }
|
||||
.record-dialog header p { margin: 3px 0 0; color: var(--text-secondary); font-size: 12px; }
|
||||
.record-dialog-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(260px, 2fr); gap: 16px; padding: 16px 20px; }
|
||||
.detail-workspace { min-height: 540px; height: 100%; display: flex; flex-direction: column; }
|
||||
.detail-header { align-items: center; }
|
||||
.detail-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
|
||||
.shortcut { margin-left: 5px; padding: 1px 5px; border: 1px solid var(--stroke); border-bottom-width: 2px; border-radius: 3px; color: var(--text-secondary); background: var(--surface-muted); font-size: 11px; }
|
||||
.detail-scroll { min-height: 0; overflow: auto; flex: 1; }
|
||||
.record-detail-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(240px, 2fr); gap: 16px; padding: 16px 20px; }
|
||||
.detail-feedback { margin: 16px 20px 0; padding: 16px; border: 1px solid var(--stroke); border-radius: 6px; background: var(--surface-muted); }
|
||||
.detail-feedback[hidden] { display: none; }
|
||||
.detail-feedback strong, .detail-feedback span { display: block; }
|
||||
.detail-feedback span { margin-top: 4px; color: var(--text-secondary); }
|
||||
.detail-feedback button { margin-top: 12px; }
|
||||
.detail-panel { min-width: 0; }
|
||||
.detail-panel h3, .record-result h3 { margin: 0 0 9px; font-size: 14px; }
|
||||
.detail-list { display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 7px 12px; margin: 0; }
|
||||
@@ -322,6 +330,7 @@
|
||||
.workspace { grid-template-columns: 1fr; }
|
||||
.stack { grid-template-rows: auto auto; }
|
||||
.record-card { min-height: 480px; }
|
||||
.workspace[data-mode="record"] .record-card { display: none; }
|
||||
.log-list { max-height: 220px; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
@@ -346,7 +355,9 @@
|
||||
.log-tools, .record-actions { justify-content: flex-start; }
|
||||
.log-list li { grid-template-columns: 58px 1fr; }
|
||||
.log-list li span:last-child { grid-column: 1 / -1; }
|
||||
.record-dialog-grid { grid-template-columns: 1fr; }
|
||||
.record-detail-grid { grid-template-columns: 1fr; }
|
||||
.detail-header-actions { align-items: stretch; justify-content: flex-start; }
|
||||
.detail-header-actions .badge { white-space: normal; }
|
||||
.evidence-grid { grid-template-columns: 1fr; }
|
||||
.detail-list { grid-template-columns: 1fr; gap: 2px; }
|
||||
.detail-list dd { margin-bottom: 7px; }
|
||||
@@ -403,6 +414,14 @@
|
||||
<option value="failed_stop">连续失败 · 轮询已停止</option>
|
||||
</select>
|
||||
</label>
|
||||
<label for="detail-state">记录详情状态(仅演示读取反馈)
|
||||
<select id="detail-state">
|
||||
<option value="normal" selected>正常</option>
|
||||
<option value="loading">加载中</option>
|
||||
<option value="error">加载失败</option>
|
||||
<option value="missing">记录不存在</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="tool-buttons">
|
||||
<button type="button" id="reset">复位假数据</button>
|
||||
<button type="button" id="close-demo">演示关闭窗口</button>
|
||||
@@ -433,8 +452,9 @@
|
||||
<button type="button" id="context-action" hidden>查看说明</button>
|
||||
</section>
|
||||
|
||||
<div class="workspace">
|
||||
<div class="stack">
|
||||
<div class="workspace" id="workspace" data-mode="live">
|
||||
<div class="left-pane">
|
||||
<div class="stack" id="live-workspace">
|
||||
<section class="card" aria-labelledby="task-title">
|
||||
<div class="card-header">
|
||||
<div><h1 id="task-title">当前任务</h1><p id="task-subtitle">尚未领取任务</p></div>
|
||||
@@ -482,6 +502,47 @@
|
||||
</div>
|
||||
<ul class="log-list" id="logs" aria-live="polite" aria-relevant="additions"></ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card detail-workspace" id="record-detail-pane" aria-labelledby="record-detail-title" aria-busy="false" hidden>
|
||||
<div class="card-header detail-header">
|
||||
<div><h1 id="record-detail-title">执行记录详情</h1><p id="record-detail-meta">选择右侧记录查看历史快照</p></div>
|
||||
<div class="detail-header-actions">
|
||||
<span class="badge" id="live-task-state" data-tone="success" aria-live="polite">实时:未启动</span>
|
||||
<button type="button" id="return-live">返回当前任务 <span class="shortcut" aria-hidden="true">Esc</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-feedback" id="detail-feedback" role="status" aria-live="polite" hidden>
|
||||
<strong id="detail-feedback-title"></strong>
|
||||
<span id="detail-feedback-text"></span>
|
||||
<button type="button" id="retry-detail" hidden>重新读取记录</button>
|
||||
</div>
|
||||
<div class="detail-scroll" id="detail-content">
|
||||
<div class="record-detail-grid">
|
||||
<section class="detail-panel" aria-labelledby="original-copy-title">
|
||||
<h3 id="original-copy-title">原始文字</h3>
|
||||
<dl class="detail-list">
|
||||
<dt>任务编号</dt><dd id="detail-task-id"></dd>
|
||||
<dt>商品标题</dt><dd id="detail-product"></dd>
|
||||
<dt>目标规格</dt><dd id="detail-spec"></dd>
|
||||
<dt>金额边界</dt><dd id="detail-cap"></dd>
|
||||
<dt>提交围栏</dt><dd id="detail-submission"></dd>
|
||||
<dt>原始说明</dt><dd id="detail-note"></dd>
|
||||
</dl>
|
||||
<ol class="timeline" id="detail-timeline" aria-label="执行时间线"></ol>
|
||||
</section>
|
||||
<section class="detail-panel" aria-labelledby="evidence-title">
|
||||
<h3 id="evidence-title">图片证据</h3>
|
||||
<div class="evidence-grid" id="detail-evidence"></div>
|
||||
</section>
|
||||
</div>
|
||||
<section class="record-result" aria-labelledby="result-title">
|
||||
<h3 id="result-title">采购结果</h3>
|
||||
<p class="result-value" id="detail-result"></p>
|
||||
<p class="result-note" id="detail-result-note"></p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card record-card" aria-labelledby="record-title">
|
||||
@@ -496,7 +557,7 @@
|
||||
</table>
|
||||
<div class="table-empty" id="record-empty" hidden>暂无执行记录。开始轮询并产生结果后会显示在这里。</div>
|
||||
</div>
|
||||
<p class="table-help" id="record-help">单击选择;双击行、按 Enter 或使用“查看所选记录”打开详情。右键也可查看。</p>
|
||||
<p class="table-help" id="record-help">单击选择;双击行、按 Enter 或使用“查看所选记录”在左侧打开详情。详情态可连续选择其他记录。</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
@@ -505,37 +566,6 @@
|
||||
<button type="button" id="record-menu-open" role="menuitem">查看记录详情</button>
|
||||
</div>
|
||||
|
||||
<dialog class="record-dialog" id="record-dialog" aria-labelledby="record-dialog-title">
|
||||
<header>
|
||||
<div><h2 id="record-dialog-title">执行记录详情</h2><p id="record-dialog-meta"></p></div>
|
||||
<button type="button" id="record-dialog-close" aria-label="关闭执行记录详情">关闭</button>
|
||||
</header>
|
||||
<div class="record-dialog-grid">
|
||||
<section class="detail-panel" aria-labelledby="original-copy-title">
|
||||
<h3 id="original-copy-title">原始文字</h3>
|
||||
<dl class="detail-list">
|
||||
<dt>任务编号</dt><dd id="detail-task-id"></dd>
|
||||
<dt>商品标题</dt><dd id="detail-product"></dd>
|
||||
<dt>目标规格</dt><dd id="detail-spec"></dd>
|
||||
<dt>金额边界</dt><dd id="detail-cap"></dd>
|
||||
<dt>提交围栏</dt><dd id="detail-submission"></dd>
|
||||
<dt>原始说明</dt><dd id="detail-note"></dd>
|
||||
</dl>
|
||||
<ol class="timeline" id="detail-timeline" aria-label="执行时间线"></ol>
|
||||
</section>
|
||||
<section class="detail-panel" aria-labelledby="evidence-title">
|
||||
<h3 id="evidence-title">图片证据</h3>
|
||||
<div class="evidence-grid" id="detail-evidence"></div>
|
||||
</section>
|
||||
</div>
|
||||
<section class="record-result" aria-labelledby="result-title">
|
||||
<h3 id="result-title">采购结果</h3>
|
||||
<p class="result-value" id="detail-result"></p>
|
||||
<p class="result-note" id="detail-result-note"></p>
|
||||
</section>
|
||||
<div class="dialog-actions"><button type="button" class="primary" id="record-dialog-done">关闭详情</button></div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="info-dialog" aria-labelledby="dialog-title">
|
||||
<div class="dialog-body">
|
||||
<h2 id="dialog-title">安全说明</h2>
|
||||
@@ -593,6 +623,8 @@
|
||||
};
|
||||
let currentRecordId = records[0]?.id || null;
|
||||
let recordFocusReturn = null;
|
||||
let workspaceMode = 'live';
|
||||
let detailRequestId = 0;
|
||||
const baseSteps = {
|
||||
idle: [],
|
||||
trial: ['打开商品(待真机判据)', '精确匹配颜色分类与尺码', '从规格面板读取单价', '截图并退出商品页'],
|
||||
@@ -671,13 +703,19 @@
|
||||
}
|
||||
function selectRecord(id, focus = false) {
|
||||
currentRecordId = id;
|
||||
let selectedRow = null;
|
||||
for (const row of $('record-rows').rows) {
|
||||
const selected = row.dataset.recordId === id;
|
||||
row.setAttribute('aria-selected', String(selected));
|
||||
row.tabIndex = selected ? 0 : -1;
|
||||
if (selected) selectedRow = row;
|
||||
if (selected && focus) row.focus();
|
||||
}
|
||||
$('view-record').disabled = !records.some((record) => record.id === id);
|
||||
if (workspaceMode === 'record') {
|
||||
recordFocusReturn = selectedRow || recordFocusReturn;
|
||||
renderRecordDetail(id);
|
||||
}
|
||||
}
|
||||
function renderRecords() {
|
||||
const rows = records.map((record) => {
|
||||
@@ -724,19 +762,65 @@
|
||||
const caption = document.createElement('figcaption'); caption.textContent = `${label} · 不访问真实拼多多`;
|
||||
figure.append(svg, caption); return figure;
|
||||
}
|
||||
function openRecord(id, source) {
|
||||
const record = records.find((item) => item.id === id); if (!record) return;
|
||||
hideRecordMenu(); currentRecordId = id; recordFocusReturn = source || [...$('record-rows').rows].find((row) => row.dataset.recordId === id) || $('view-record');
|
||||
function showDetailFeedback(title, text, retry = false) {
|
||||
$('detail-feedback-title').textContent = title;
|
||||
$('detail-feedback-text').textContent = text;
|
||||
$('retry-detail').hidden = !retry;
|
||||
$('detail-feedback').hidden = false;
|
||||
$('detail-content').hidden = true;
|
||||
}
|
||||
async function renderRecordDetail(id) {
|
||||
const requestId = ++detailRequestId;
|
||||
const prototypeState = $('detail-state').value;
|
||||
const record = records.find((item) => item.id === id);
|
||||
$('record-detail-pane').setAttribute('aria-busy', String(prototypeState === 'loading' || prototypeState === 'normal'));
|
||||
if (prototypeState === 'loading') {
|
||||
showDetailFeedback('正在读取执行记录', `正在按稳定记录 ID ${id} 读取文字、图片索引和采购结果。`);
|
||||
return;
|
||||
}
|
||||
if (prototypeState === 'error') {
|
||||
showDetailFeedback('执行记录加载失败', '当前任务与右侧列表不受影响。可安全重试读取同一记录,不会重新执行采购。', true);
|
||||
$('record-detail-pane').setAttribute('aria-busy', 'false');
|
||||
return;
|
||||
}
|
||||
if (prototypeState === 'missing' || !record) {
|
||||
showDetailFeedback('执行记录已不存在', '记录可能已归档或当前账号无权查看。请返回当前任务;不会用其他记录补齐内容。');
|
||||
$('record-detail-pane').setAttribute('aria-busy', 'false');
|
||||
return;
|
||||
}
|
||||
showDetailFeedback('正在读取执行记录', `正在加载 ${record.taskId} 的已保存证据。`);
|
||||
await Promise.resolve();
|
||||
if (requestId !== detailRequestId || workspaceMode !== 'record' || currentRecordId !== id) return;
|
||||
const status = recordStatus[record.status] || {label:record.status};
|
||||
$('record-dialog-title').textContent = record.title;
|
||||
$('record-dialog-meta').textContent = `${record.taskId} · ${formatRecordTime(record.collectedAt)} · ${status.label}`;
|
||||
$('record-detail-title').textContent = record.title;
|
||||
$('record-detail-meta').textContent = `${record.taskId} · ${formatRecordTime(record.collectedAt)} · ${status.label}`;
|
||||
$('detail-task-id').textContent = record.taskId; $('detail-product').textContent = record.title; $('detail-spec').textContent = record.spec;
|
||||
$('detail-cap').textContent = record.cap; $('detail-submission').textContent = record.submission; $('detail-note').textContent = record.note;
|
||||
$('detail-timeline').replaceChildren(...record.timeline.map((value) => { const li = document.createElement('li'); li.textContent = value; return li; }));
|
||||
if (record.evidence.length) $('detail-evidence').replaceChildren(...record.evidence.map(createEvidence));
|
||||
else { const empty = document.createElement('div'); empty.className = 'preview-empty'; const strong = document.createElement('strong'); strong.textContent = '暂无可信图片'; empty.append(strong, '记录保留原始文字,不用其他图片凑合。'); $('detail-evidence').replaceChildren(empty); }
|
||||
$('detail-result').textContent = record.result; $('detail-result-note').textContent = record.resultNote;
|
||||
$('record-dialog').showModal();
|
||||
$('detail-feedback').hidden = true; $('detail-content').hidden = false;
|
||||
$('record-detail-pane').setAttribute('aria-busy', 'false');
|
||||
}
|
||||
function openRecord(id, source) {
|
||||
const record = records.find((item) => item.id === id); if (!record) return;
|
||||
hideRecordMenu(); currentRecordId = id;
|
||||
recordFocusReturn = source || [...$('record-rows').rows].find((row) => row.dataset.recordId === id) || $('view-record');
|
||||
workspaceMode = 'record'; $('workspace').dataset.mode = 'record';
|
||||
$('live-workspace').hidden = true; $('record-detail-pane').hidden = false;
|
||||
renderRecordDetail(id);
|
||||
$('return-live').focus();
|
||||
$('announcer').textContent = `已在左侧打开 ${record.title} 的执行记录详情。按 Escape 或返回当前任务按钮返回。`;
|
||||
}
|
||||
function showLiveWorkspace() {
|
||||
if (workspaceMode !== 'record') return;
|
||||
detailRequestId += 1; workspaceMode = 'live'; $('workspace').dataset.mode = 'live';
|
||||
$('record-detail-pane').hidden = true; $('live-workspace').hidden = false;
|
||||
const currentRow = [...$('record-rows').rows].find((row) => row.dataset.recordId === currentRecordId);
|
||||
const focusTarget = currentRow || (recordFocusReturn?.isConnected ? recordFocusReturn : $('view-record'));
|
||||
focusTarget?.focus();
|
||||
$('announcer').textContent = '已返回当前任务;执行记录选择和列表位置已保留。';
|
||||
}
|
||||
function render(key, announce = true) {
|
||||
const s = scenarios[key];
|
||||
@@ -760,6 +844,7 @@
|
||||
renderSteps(s.kind, s.active);
|
||||
$('countdown').textContent = s.countdown; $('failure-count').textContent = s.fail; $('completed-count').textContent = String(s.count || 0);
|
||||
$('session-state').textContent = s.session; $('current-step').textContent = s.step;
|
||||
$('live-task-state').textContent = `实时:${s.session} · ${s.step}`; $('live-task-state').dataset.tone = s.tone;
|
||||
$('primary-action').textContent = sessionControl.label; $('primary-action').disabled = !sessionControl.enabled;
|
||||
$('action-reason').textContent = sessionControl.reason;
|
||||
$('context-action').hidden = !s.safeAction; $('context-action').textContent = s.safeAction ? s.action : '';
|
||||
@@ -777,7 +862,7 @@
|
||||
|
||||
$('scenario').addEventListener('change', (e) => render(e.target.value));
|
||||
$('theme').addEventListener('change', (e) => { document.documentElement.dataset.theme = e.target.value; $('announcer').textContent = `主题已切换为${e.target.options[e.target.selectedIndex].text}`; });
|
||||
$('reset').addEventListener('click', () => { $('scenario').value = 'idle'; $('logs').replaceChildren(); currentRecordId = records[0]?.id || null; renderRecords(); render('idle'); appendLog('状态', '假数据已复位。'); });
|
||||
$('reset').addEventListener('click', () => { showLiveWorkspace(); $('scenario').value = 'idle'; $('detail-state').value = 'normal'; $('logs').replaceChildren(); currentRecordId = records[0]?.id || null; renderRecords(); render('idle'); appendLog('状态', '假数据已复位。'); });
|
||||
$('primary-action').addEventListener('click', () => {
|
||||
const next = $('scenario').value === 'idle' ? 'polling' : 'idle'; $('scenario').value = next; render(next);
|
||||
});
|
||||
@@ -790,11 +875,16 @@
|
||||
});
|
||||
$('view-record').addEventListener('click', () => openRecord(currentRecordId, [...$('record-rows').rows].find((row) => row.dataset.recordId === currentRecordId)));
|
||||
$('record-menu-open').addEventListener('click', () => openRecord(currentRecordId, [...$('record-rows').rows].find((row) => row.dataset.recordId === currentRecordId)));
|
||||
$('record-dialog-close').addEventListener('click', () => $('record-dialog').close());
|
||||
$('record-dialog-done').addEventListener('click', () => $('record-dialog').close());
|
||||
$('record-dialog').addEventListener('close', () => { if (recordFocusReturn?.isConnected) recordFocusReturn.focus(); });
|
||||
$('return-live').addEventListener('click', showLiveWorkspace);
|
||||
$('detail-state').addEventListener('change', () => { if (workspaceMode === 'record') renderRecordDetail(currentRecordId); });
|
||||
$('retry-detail').addEventListener('click', () => { $('detail-state').value = 'normal'; renderRecordDetail(currentRecordId); });
|
||||
document.addEventListener('pointerdown', (event) => { if (!$('record-menu').hidden && !$('record-menu').contains(event.target)) hideRecordMenu(); });
|
||||
document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && !$('record-menu').hidden) hideRecordMenu(); });
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (!$('record-menu').hidden) { event.preventDefault(); hideRecordMenu(); return; }
|
||||
if ($('info-dialog').open) return;
|
||||
if (workspaceMode === 'record') { event.preventDefault(); showLiveWorkspace(); }
|
||||
});
|
||||
window.addEventListener('resize', hideRecordMenu);
|
||||
$('export-log').addEventListener('click', exportDemo);
|
||||
$('close-demo').addEventListener('click', () => {
|
||||
|
||||
+7
-5
@@ -96,14 +96,16 @@
|
||||
|
||||
- 顶部状态区:web、设备、拼多多 App 实际 / 已取证版本和会话状态;开始 / 停止轮询固定在
|
||||
最右侧且只控制轮询会话。版本不一致时 fail closed 并提示重新取证。
|
||||
- 宽屏三块工作区:左上当前任务,左下滚动日志,右侧执行记录;窄屏按此顺序改为单列。
|
||||
- 宽屏是主从工作区:左侧在“当前任务 + 滚动日志”和“执行记录详情”之间切换,右侧执行记录
|
||||
保持可见;窄屏进入详情时暂时收起记录表,返回后恢复列表现场。
|
||||
- 当前任务左侧显示商品、规格、数量、**当前是第一趟还是第二趟**、步骤和剩余租约;右侧约
|
||||
1/3 显示可选商品 / 证据预览。无可信图片时保持空态,有图必须标来源与采集时间。
|
||||
- 滚动日志标题区同时显示下次轮询、连续失败和本次完成,不再使用独立会话卡或底部控制条。
|
||||
- 执行记录只显示标题、状态两列,任务编号与时间放在标题次行,按采集时间倒序。单击选择;
|
||||
双击、Enter、可见“查看所选记录”或右键打开详情。
|
||||
- 记录详情上部左侧显示原始文字、右侧显示图片证据,下部显示结构化采购结果;关闭后焦点
|
||||
返回原行。图片缺失不使用其他来源凑合。
|
||||
双击、Enter、可见“查看所选记录”或右键在左侧打开详情;详情态单击另一行直接更新详情。
|
||||
- 记录详情不是模态框:上部左侧显示原始文字、右侧显示图片证据,下部显示结构化采购结果;
|
||||
可见“返回当前任务”与 `Esc` 均能返回并恢复当前行焦点。加载失败、无图和记录不存在有明确
|
||||
状态,图片缺失不使用其他来源凑合。
|
||||
- **待人工时整页显著变色并说明缺什么**,不要让执行员盯着一个静止画面猜。
|
||||
- 关闭窗口即停止轮询;连续失败达阈值自动停止并显示原因。
|
||||
|
||||
@@ -142,7 +144,7 @@
|
||||
| `CurrentTaskPanel` | desk 采购执行页 | 当前任务文字、步骤与带来源的可选图片证据 |
|
||||
| `ExecutionLog` | desk 采购执行页 | 最新在上的滚动日志与假诊断导出 |
|
||||
| `ExecutionRecordTable` | desk 采购执行页 | 标题 / 状态两列、时间倒序和稳定记录 ID |
|
||||
| `ExecutionRecordDialog` | desk 采购执行页 | 原始文字、图片证据、采购结果与焦点恢复 |
|
||||
| `ExecutionRecordDetailPanel` | desk 采购执行页 | 左侧内联原始文字、图片证据、采购结果、读取状态与返回焦点 |
|
||||
|
||||
## 六、原型
|
||||
|
||||
|
||||
+42
-7
@@ -168,14 +168,49 @@ desk 端将使用 PySide6 / Qt Widgets,但当前没有可确认的桌面应用
|
||||
|
||||
## 执行记录
|
||||
|
||||
### 2026-08-03 · 第三轮内联详情待实现
|
||||
### 2026-08-03 · 第三轮左侧内联详情实现
|
||||
|
||||
- 任务保持 `DOING`。本轮拟将执行记录详情从模态对话框改为左侧双视图工作区;只修改 T-006
|
||||
写路径内的原型与同步文档,不扩展 API、真机或生产 PySide6 范围。
|
||||
- 计划验证双击 / Enter / 可见命令 / 右键入口、详情态连续选行、返回按钮、`Esc` 层级、焦点
|
||||
恢复、紧凑宽度单视图,以及加载 / 失败 / 缺图 / 记录不存在状态。
|
||||
- PySide6 交接将把左栏映射为 `QStackedWidget`,整体映射为 `QSplitter`,右侧继续使用
|
||||
`QTableView` + 稳定 ID 模型;详情异步结果必须带请求代次并在 GUI 线程丢弃过期回调。
|
||||
- 任务继续保持 `DOING`:第三轮自动验证已经完成,但 `needs_human_review: true`,仍需人确认
|
||||
工作区切换、返回路径、宽屏连续查看与紧凑窗口取舍。
|
||||
- 执行记录详情已从模态 `dialog` 移到左侧双视图工作区。默认 `LIVE_TASK` 保持当前任务和滚动
|
||||
日志;双击、Enter、可见命令或右键进入 `RECORD_DETAIL(record_id)`,右侧宽屏列表不被遮挡。
|
||||
- 详情态单击或方向键选择另一行会按稳定记录 ID 更新左侧详情。可见“返回当前任务”按钮和
|
||||
`Esc` 共用返回命令,保留列表选择 / 滚动,并把焦点还给当前记录行。
|
||||
- `Esc` 先关闭右键菜单;信息对话框打开时不触发工作区返回。顶部连接、会话和安全 Banner
|
||||
始终保留,详情头部另显示实时会话摘要,证明查看历史不会冻结当前任务状态表达。
|
||||
- 原型控制器可演示详情正常、加载中、加载失败和记录不存在;缺图记录仍显示可信证据空态。
|
||||
重新读取只读取同一记录,不执行采购。渲染带请求代次,切换记录或返回后丢弃旧异步结果。
|
||||
- `windows-ui-ux` 使本轮采用非模态主从工作区、可见返回命令、分层 Escape、稳定选择与焦点
|
||||
恢复,并明确紧凑宽度采用单视图,而不是把两个高密度面板纵向无限堆叠。
|
||||
|
||||
**第三轮 HTML → PySide6 / Qt Widgets 交接增量**
|
||||
|
||||
| 原型项 | 核心交互契约 | 推荐 Qt Widgets 映射 | 状态 / 线程要求 |
|
||||
| --- | --- | --- | --- |
|
||||
| 左侧双视图 | 当前任务 / 记录详情互斥;顶部状态不在切换区 | 左栏 `QStackedWidget`,整体 `QSplitter` | 页面切换不销毁当前任务模型、日志或列表状态 |
|
||||
| 详情入口与连续选行 | 双击、Enter、按钮、右键共用命令;详情态换行立即换详情 | 共享 `QAction`;`QTableView.activated` 与按钮触发 | 记录身份使用模型中的稳定 ID,不使用代理行号 |
|
||||
| 返回当前任务 | 可见按钮与 Escape 共用;恢复当前行焦点 | 共享返回 `QAction` + `QShortcut(Escape)` | 仅在无更高层浮层时启用;按稳定 ID 重新定位代理行 |
|
||||
| 详情读取 | 加载 / 失败 / 缺图 / 不存在状态明确 | 状态页或 `QStackedWidget` + 只读详情控件 | worker 结果携带 record ID 与 request ID;GUI 线程拒绝过期结果 |
|
||||
| 紧凑宽度 | 详情态暂时隐藏记录表,返回后恢复现场 | 响应窗口宽度调整 `QSplitter` 可见性 | 隐藏不重建模型,不丢选择、排序或滚动位置 |
|
||||
|
||||
**第三轮已运行验证**
|
||||
|
||||
- Node 静态检查:执行页 78 个 id 无重复,内联脚本可编译,执行记录详情 `dialog` 已不存在。
|
||||
- Playwright 在 1440 px 验证双击、Enter、可见按钮 / 右键共用内联详情;详情态连续选行会更新
|
||||
标题,返回按钮与 `Esc` 恢复当前记录行焦点;右键菜单和信息对话框的 Escape 层级正确。
|
||||
- 详情正常、加载中、加载失败 / 重新读取、记录不存在、无图片均可演示;查看详情期间切换到
|
||||
第一趟试选,顶部 Banner 和详情实时徽标继续更新,且详情不退出。
|
||||
- 375 / 768 / 1024 / 1440 px 均无页面级水平溢出;375 / 768 px 详情态隐藏记录表,返回后
|
||||
恢复表格与唯一选择;1024 / 1440 px 保持宽屏主从布局。浏览器 0 error / 0 warning,页面无
|
||||
外部资源请求。
|
||||
- `python scripts/validate_agent_context.py` 与 `git diff --check`:提交前运行并记录最终结果。
|
||||
|
||||
**第三轮未验证与待人工确认**
|
||||
|
||||
- 没有生产 PySide6、Model/View、worker、图片持久化、API、ADB 或真机行为;浏览器原型不能
|
||||
证明原生 Qt 焦点、Narrator、UI Automation、DPI、线程或性能。
|
||||
- 待人确认:宽屏查看详情时是否继续保留右表;紧凑宽度是否接受单视图;返回按钮位置、实时
|
||||
状态徽标密度,以及详情加载 / 不存在文案。人工确认前任务不得改为 `DONE`。
|
||||
|
||||
### 2026-08-03 · 第二轮采购工具工作区实现
|
||||
|
||||
|
||||
Reference in New Issue
Block a user