docs(prototype): confirm p0 web and app flows

This commit is contained in:
QiuSW
2026-07-26 13:19:16 +08:00
parent ab21219a07
commit 2b265c92fc
19 changed files with 2170 additions and 16 deletions
+328
View File
@@ -0,0 +1,328 @@
<!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>
* { box-sizing: border-box; }
:root {
color-scheme: light;
--ink: #18201d;
--muted: #5b6862;
--line: #cfd7d3;
--surface: #fff;
--canvas: #f3f5f4;
--brand: #0b6b50;
--brand-dark: #07513d;
--info: #185b8f;
--warn: #865f12;
--danger: #9b3030;
}
body {
margin: 0;
min-width: 280px;
color: var(--ink);
background: var(--canvas);
font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
letter-spacing: 0;
}
button, input, select { font: inherit; min-height: 42px; }
button:focus-visible, input:focus-visible, select:focus-visible, a:focus-visible {
outline: 3px solid rgba(26, 115, 184, .28);
outline-offset: 2px;
}
.prototype {
position: fixed;
inset: 0 0 auto;
z-index: 10;
min-height: 32px;
padding: 6px 16px;
color: #fff;
background: #24312c;
text-align: center;
font-size: 12px;
font-weight: 700;
}
.app { min-height: 100vh; padding-top: 32px; }
header {
height: 58px;
display: flex;
align-items: center;
gap: 28px;
padding: 0 clamp(16px, 4vw, 40px);
border-bottom: 1px solid var(--line);
background: #fff;
}
.brand { font-size: 16px; font-weight: 750; white-space: nowrap; }
nav { display: flex; height: 100%; gap: 6px; }
nav a {
display: grid;
place-items: center;
padding: 0 12px;
color: #3d4b45;
text-decoration: none;
border-bottom: 3px solid transparent;
}
nav a[aria-current="page"] { color: var(--brand-dark); border-color: var(--brand); font-weight: 700; }
.user { margin-left: auto; color: var(--muted); }
main { width: min(100% - 32px, 1180px); margin: 0 auto; padding: 26px 0 56px; }
.title-row { display: flex; justify-content: space-between; gap: 16px; align-items: center; margin-bottom: 18px; }
h1 { margin: 0; font-size: 24px; line-height: 1.3; }
.subtitle { margin: 3px 0 0; color: var(--muted); }
.button, button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 13px;
border: 1px solid #91a099;
border-radius: 5px;
color: #26352f;
background: #fff;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
}
.primary { border-color: var(--brand); color: #fff; background: var(--brand); font-weight: 700; }
.primary:hover { background: var(--brand-dark); }
.filters {
display: grid;
grid-template-columns: minmax(180px, 1fr) 180px 130px;
gap: 10px;
padding: 14px;
border: 1px solid var(--line);
border-bottom: 0;
border-radius: 8px 8px 0 0;
background: #fff;
}
input, select {
width: 100%;
padding: 8px 10px;
border: 1px solid #9eaaa4;
border-radius: 5px;
color: var(--ink);
background: #fff;
}
.table-wrap {
border: 1px solid var(--line);
border-radius: 0 0 8px 8px;
background: #fff;
overflow: hidden;
}
table { width: 100%; border-collapse: collapse; table-layout: fixed; }
th, td { padding: 13px 12px; border-bottom: 1px solid #e1e6e3; text-align: left; vertical-align: middle; overflow-wrap: anywhere; }
th { color: #45534d; background: #f8faf9; font-size: 13px; font-weight: 700; }
tbody tr:hover { background: #f8fbfa; }
tbody tr:last-child td { border-bottom: 0; }
.task-title { display: block; color: var(--ink); font-weight: 700; }
.task-id { color: var(--muted); font-size: 12px; }
.status {
display: inline-flex;
min-height: 28px;
align-items: center;
padding: 3px 8px;
border: 1px solid #9eaaa4;
border-radius: 5px;
background: #f8faf9;
font-size: 12px;
font-weight: 700;
}
.status.pending { color: #6a4a0b; border-color: #d6b970; background: #fff8e5; }
.status.running { color: #174f79; border-color: #8eb7d4; background: #eef7fd; }
.status.success { color: #07513d; border-color: #7db6a3; background: #eaf7f1; }
.status.failed { color: #842828; border-color: #d99c9c; background: #fff1f1; }
.detail-link { color: var(--brand-dark); font-weight: 700; }
.state-panel {
display: none;
min-height: 240px;
place-items: center;
padding: 32px 18px;
text-align: center;
}
.state-panel.visible { display: grid; }
.state-box { max-width: 520px; }
.state-box h2 { margin: 0 0 6px; font-size: 18px; }
.state-box p { margin: 0 0 16px; color: var(--muted); }
.stale {
display: none;
margin: 0 0 12px;
padding: 10px 12px;
border-left: 4px solid var(--warn);
color: #684a0f;
background: #fff8e5;
}
.demo-tools { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.demo-tools button { min-height: 36px; padding-block: 6px; font-size: 13px; }
.demo-tools button[aria-pressed="true"] { color: #fff; border-color: #394a43; background: #394a43; }
.updated { margin: 12px 0 0; color: var(--muted); font-size: 13px; }
@media (max-width: 760px) {
main { width: min(100% - 20px, 1180px); padding-top: 20px; }
.filters { grid-template-columns: 1fr 1fr; }
.filters button { grid-column: 1 / -1; }
.table-wrap { border-radius: 0 0 8px 8px; }
table, tbody { display: block; }
thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
tr { display: grid; grid-template-columns: 1fr 1fr; padding: 12px; border-bottom: 1px solid var(--line); }
tbody tr:last-child { border-bottom: 0; }
td { display: block; padding: 6px; border: 0; }
td::before { content: attr(data-label); display: block; margin-bottom: 2px; color: var(--muted); font-size: 11px; font-weight: 700; }
td:first-child, td:last-child { grid-column: 1 / -1; }
}
@media (max-width: 480px) {
.prototype { padding-inline: 2px; font-size: 10px; white-space: nowrap; }
header { gap: 10px; }
.brand { font-size: 14px; }
nav a { padding-inline: 8px; }
nav a:last-child { display: none; }
.user { display: none; }
.title-row { align-items: stretch; flex-direction: column; }
.primary { width: 100%; }
.filters { grid-template-columns: 1fr; }
.filters button { grid-column: auto; }
tr { grid-template-columns: 1fr; }
td, td:first-child, td:last-child { grid-column: auto; }
}
</style>
</head>
<body>
<div class="prototype">PROTOTYPE - 仅供确认布局和交互,不是生产实现</div>
<div class="app">
<header>
<div class="brand">采购任务管理</div>
<nav aria-label="主导航">
<a href="admin-task-list.html" aria-current="page">任务列表</a>
<a href="admin-task-create.html">新建任务</a>
</nav>
<div class="user">演示管理员</div>
</header>
<main>
<div class="title-row">
<div>
<h1>采购任务</h1>
<p class="subtitle">按最近创建时间查看执行状态和证据</p>
</div>
<a class="button primary" href="admin-task-create.html">新建任务</a>
</div>
<p id="stale" class="stale" role="status">刷新失败,当前显示的是上次成功加载的内容,数据可能已过期。</p>
<form class="filters" id="filters">
<input type="search" aria-label="搜索任务编号或标题" placeholder="搜索编号或标题">
<select aria-label="筛选任务状态">
<option>全部状态</option>
<option>待领取</option>
<option>执行中</option>
<option>等待人工确认</option>
<option>验证完成</option>
<option>失败</option>
</select>
<button type="submit">应用筛选</button>
</form>
<section class="table-wrap" aria-label="任务列表内容">
<div id="normal">
<table>
<thead>
<tr>
<th style="width:30%">任务</th>
<th style="width:16%">状态</th>
<th style="width:15%">执行设备</th>
<th style="width:22%">更新时间</th>
<th style="width:17%">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="任务"><span class="task-title">演示桌面收纳盒</span><span class="task-id">RB-DEMO-1004</span></td>
<td data-label="状态"><span class="status success">验证完成</span></td>
<td data-label="执行设备">演示设备 A</td>
<td data-label="更新时间"><time datetime="2026-07-25T10:24:00+08:00">今天 10:24</time></td>
<td data-label="操作"><a class="detail-link" href="admin-task-detail.html">查看详情</a></td>
</tr>
<tr>
<td data-label="任务"><span class="task-title">演示纯棉毛巾</span><span class="task-id">RB-DEMO-1003</span></td>
<td data-label="状态"><span class="status running">执行中</span></td>
<td data-label="执行设备">演示设备 B</td>
<td data-label="更新时间"><time datetime="2026-07-25T10:12:00+08:00">今天 10:12</time></td>
<td data-label="操作"><a class="detail-link" href="admin-task-detail.html">查看详情</a></td>
</tr>
<tr>
<td data-label="任务"><span class="task-title">演示文件收纳袋</span><span class="task-id">RB-DEMO-1002</span></td>
<td data-label="状态"><span class="status pending">待领取</span></td>
<td data-label="执行设备">尚未分配</td>
<td data-label="更新时间"><time datetime="2026-07-25T09:48:00+08:00">今天 09:48</time></td>
<td data-label="操作"><a class="detail-link" href="admin-task-detail.html">查看详情</a></td>
</tr>
<tr>
<td data-label="任务"><span class="task-title">演示替换刷头</span><span class="task-id">RB-DEMO-1001</span></td>
<td data-label="状态"><span class="status failed">已停止 · 页面未知</span></td>
<td data-label="执行设备">演示设备 A</td>
<td data-label="更新时间"><time datetime="2026-07-25T09:31:00+08:00">今天 09:31</time></td>
<td data-label="操作"><a class="detail-link" href="admin-task-detail.html">查看详情</a></td>
</tr>
</tbody>
</table>
</div>
<div id="state-panel" class="state-panel" aria-live="polite">
<div class="state-box">
<h2 id="state-title"></h2>
<p id="state-copy"></p>
<button id="state-action" type="button"></button>
</div>
</div>
</section>
<p class="updated">共 4 条演示任务 · 手动刷新,不自动轮询</p>
<div class="demo-tools" aria-label="原型页面状态">
<button type="button" data-state="normal" aria-pressed="true">有数据</button>
<button type="button" data-state="loading" aria-pressed="false">加载中</button>
<button type="button" data-state="empty" aria-pressed="false">空状态</button>
<button type="button" data-state="stale" aria-pressed="false">刷新失败</button>
<button type="button" data-state="failed" aria-pressed="false">加载失败</button>
<button type="button" data-state="forbidden" aria-pressed="false">权限不足</button>
</div>
</main>
</div>
<script>
const normal = document.querySelector('#normal');
const panel = document.querySelector('#state-panel');
const stale = document.querySelector('#stale');
const stateTitle = document.querySelector('#state-title');
const stateCopy = document.querySelector('#state-copy');
const stateAction = document.querySelector('#state-action');
const states = {
loading: ['正在加载任务', '筛选条件已保留,请稍候。', '取消加载'],
empty: ['还没有采购任务', '新建任务后,它会显示在这里。', '新建任务'],
failed: ['任务加载失败', '当前没有可展示的缓存内容,请检查网络后重试。', '重试'],
forbidden: ['没有查看权限', '当前账号无权访问任务列表,页面不会显示任何任务内容。', '返回登录']
};
function showState(state) {
document.querySelectorAll('.demo-tools button').forEach((button) => {
button.setAttribute('aria-pressed', String(button.dataset.state === state));
});
stale.style.display = state === 'stale' ? 'block' : 'none';
const showRows = state === 'normal' || state === 'stale';
normal.style.display = showRows ? 'block' : 'none';
panel.classList.toggle('visible', !showRows);
if (!showRows) {
const values = states[state];
stateTitle.textContent = values[0];
stateCopy.textContent = values[1];
stateAction.textContent = values[2];
}
}
document.querySelector('.demo-tools').addEventListener('click', (event) => {
if (event.target.dataset.state) showState(event.target.dataset.state);
});
stateAction.addEventListener('click', () => {
if (stateAction.textContent === '新建任务') window.location.href = 'admin-task-create.html';
else if (stateAction.textContent === '返回登录') window.location.href = 'admin-login.html';
else showState('normal');
});
document.querySelector('#filters').addEventListener('submit', (event) => {
event.preventDefault();
showState('loading');
window.setTimeout(() => showState('normal'), 500);
});
</script>
</body>
</html>