feat(prototype): show record details inline
This commit is contained in:
+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', () => {
|
||||
|
||||
Reference in New Issue
Block a user