Capture the approved design for same-main-plate public driver history (list by pickup/delivery day, today shortcuts, cross-plan feedback). Co-authored-by: Cursor <cursoragent@cursor.com>
658 lines
19 KiB
HTML
658 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
|
||
<title>司机链接 · 运输记录 Demo</title>
|
||
<style>
|
||
:root {
|
||
--green: #059669;
|
||
--green-deep: #047857;
|
||
--green-soft: #ecfdf5;
|
||
--text: #1e293b;
|
||
--muted: #64748b;
|
||
--line: #e2e8f0;
|
||
--bg: #f1f5f9;
|
||
--warn: #d97706;
|
||
--danger: #dc2626;
|
||
--card: #fff;
|
||
--radius: 12px;
|
||
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
||
}
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", sans-serif;
|
||
background: #0f172a;
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
}
|
||
.stage {
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 24px 16px 48px;
|
||
}
|
||
.stage-head {
|
||
color: #e2e8f0;
|
||
margin-bottom: 20px;
|
||
}
|
||
.stage-head h1 {
|
||
margin: 0 0 8px;
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
}
|
||
.stage-head p {
|
||
margin: 0;
|
||
color: #94a3b8;
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
}
|
||
.phones {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
.phone-wrap {
|
||
background: #1e293b;
|
||
border-radius: 20px;
|
||
padding: 12px;
|
||
border: 1px solid #334155;
|
||
}
|
||
.phone-label {
|
||
color: #cbd5e1;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
margin: 0 0 10px 4px;
|
||
}
|
||
.phone {
|
||
width: 100%;
|
||
max-width: 390px;
|
||
margin: 0 auto;
|
||
background: var(--bg);
|
||
border-radius: 18px;
|
||
overflow: hidden;
|
||
min-height: 720px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: 0 20px 40px rgba(0,0,0,.35);
|
||
position: relative;
|
||
}
|
||
.nav {
|
||
background: var(--green);
|
||
color: #fff;
|
||
height: 48px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
}
|
||
.nav .back {
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: transparent;
|
||
border: 0;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
padding: 8px;
|
||
}
|
||
.scroll {
|
||
flex: 1;
|
||
overflow: auto;
|
||
padding: 12px 12px 96px;
|
||
}
|
||
.scroll.no-footer-pad { padding-bottom: 12px; }
|
||
.card {
|
||
background: var(--card);
|
||
border-radius: var(--radius);
|
||
margin-bottom: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid #eef2f7;
|
||
}
|
||
.card-title {
|
||
padding: 10px 14px 0;
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
font-weight: 600;
|
||
}
|
||
.row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
border-bottom: 1px solid #f1f5f9;
|
||
font-size: 14px;
|
||
}
|
||
.row:last-child { border-bottom: 0; }
|
||
.row .k { color: var(--muted); flex-shrink: 0; }
|
||
.row .v { color: var(--text); text-align: right; word-break: break-all; }
|
||
.empty {
|
||
padding: 64px 24px;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
}
|
||
.empty .icon {
|
||
width: 64px;
|
||
height: 64px;
|
||
margin: 0 auto 16px;
|
||
border-radius: 50%;
|
||
background: #e2e8f0;
|
||
display: grid;
|
||
place-items: center;
|
||
font-size: 28px;
|
||
color: #94a3b8;
|
||
}
|
||
.empty .title {
|
||
font-size: 15px;
|
||
color: #475569;
|
||
margin-bottom: 6px;
|
||
}
|
||
.footer {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
padding: 10px 12px calc(10px + var(--safe-bottom));
|
||
background: rgba(255,255,255,.96);
|
||
border-top: 1px solid var(--line);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
.btn {
|
||
height: 44px;
|
||
border-radius: 999px;
|
||
border: 0;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
}
|
||
.btn-primary {
|
||
background: var(--green);
|
||
color: #fff;
|
||
}
|
||
.btn-primary:active { background: var(--green-deep); }
|
||
.btn-ghost {
|
||
background: #fff;
|
||
color: var(--green);
|
||
border: 1px solid #a7f3d0;
|
||
}
|
||
.btn-ghost:active { background: var(--green-soft); }
|
||
.btn-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
.filters {
|
||
background: var(--card);
|
||
border-radius: var(--radius);
|
||
padding: 12px;
|
||
margin-bottom: 12px;
|
||
border: 1px solid #eef2f7;
|
||
}
|
||
.filter-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.filter-row:last-child { margin-bottom: 0; }
|
||
.chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--line);
|
||
background: #fff;
|
||
font-size: 13px;
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.chip.on {
|
||
background: var(--green-soft);
|
||
border-color: #6ee7b7;
|
||
color: var(--green-deep);
|
||
font-weight: 600;
|
||
}
|
||
.chip input { display: none; }
|
||
.date-inputs {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr;
|
||
gap: 8px;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
.date-inputs input {
|
||
height: 36px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 8px;
|
||
padding: 0 10px;
|
||
font-size: 13px;
|
||
color: var(--text);
|
||
background: #fff;
|
||
width: 100%;
|
||
}
|
||
.date-inputs span { color: var(--muted); font-size: 12px; }
|
||
.hint {
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
margin-top: 8px;
|
||
}
|
||
.record {
|
||
background: var(--card);
|
||
border-radius: var(--radius);
|
||
border: 1px solid #eef2f7;
|
||
padding: 14px;
|
||
margin-bottom: 10px;
|
||
cursor: pointer;
|
||
transition: border-color .15s, box-shadow .15s;
|
||
}
|
||
.record:active, .record:hover {
|
||
border-color: #6ee7b7;
|
||
box-shadow: 0 0 0 3px rgba(16,185,129,.12);
|
||
}
|
||
.record-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
align-items: flex-start;
|
||
margin-bottom: 8px;
|
||
}
|
||
.record-no {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
}
|
||
.tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 22px;
|
||
padding: 0 8px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
flex-shrink: 0;
|
||
}
|
||
.tag.ok { background: var(--green); }
|
||
.tag.arch { background: #64748b; }
|
||
.tag.void { background: var(--danger); }
|
||
.tag.warn { background: var(--warn); }
|
||
.record-meta {
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
line-height: 1.55;
|
||
}
|
||
.record-meta strong { color: var(--text); font-weight: 500; }
|
||
.mini-tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.mini-tabs button {
|
||
border: 1px solid #334155;
|
||
background: #0f172a;
|
||
color: #94a3b8;
|
||
border-radius: 999px;
|
||
padding: 8px 12px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.mini-tabs button.active {
|
||
background: var(--green);
|
||
border-color: var(--green);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
.note {
|
||
margin-top: 18px;
|
||
padding: 14px 16px;
|
||
background: #1e293b;
|
||
border: 1px solid #334155;
|
||
border-radius: 12px;
|
||
color: #cbd5e1;
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
}
|
||
.note strong { color: #f8fafc; }
|
||
.screen { display: none; flex: 1; flex-direction: column; min-height: 0; }
|
||
.screen.active { display: flex; }
|
||
.product {
|
||
padding: 12px 14px;
|
||
border-top: 1px solid #f1f5f9;
|
||
}
|
||
.product-name { font-weight: 600; margin-bottom: 6px; }
|
||
.product-meta { font-size: 13px; color: var(--muted); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="stage">
|
||
<div class="stage-head">
|
||
<h1>司机链接 ·「运输记录」交互 Demo</h1>
|
||
<p>
|
||
列表默认<strong>今天</strong>。筛选为:单日日期框 +「今天 / 明天 / 后天」快捷按钮。
|
||
命中:取货日或送货日等于所选日。按<strong>主车车牌</strong>聚合。
|
||
</p>
|
||
<div class="mini-tabs" id="jumpTabs">
|
||
<button type="button" class="active" data-jump="A">A · 正常详情</button>
|
||
<button type="button" data-jump="B">B · 已归档详情</button>
|
||
<button type="button" data-jump="C">C · 运输记录列表</button>
|
||
<button type="button" data-jump="D">D · 作废详情</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="phones">
|
||
<!-- Phone interactive -->
|
||
<div class="phone-wrap">
|
||
<div class="phone-label">可交互 Demo(点底部按钮 / 点记录)</div>
|
||
<div class="phone" id="phone">
|
||
<!-- Screen: active detail -->
|
||
<div class="screen active" data-screen="detail-active">
|
||
<div class="nav">运输计划</div>
|
||
<div class="scroll">
|
||
<div class="card">
|
||
<div class="row"><span class="k">计划编号</span><span class="v">YS20260821001</span></div>
|
||
<div class="row"><span class="k">计划日期</span><span class="v">2026-08-21</span></div>
|
||
<div class="row"><span class="k">车牌</span><span class="v">苏A·D12345</span></div>
|
||
<div class="row"><span class="k">司机</span><span class="v">张师傅</span></div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">取货信息</div>
|
||
<div class="row"><span class="k">取货地址</span><span class="v">南京市江宁区××仓库</span></div>
|
||
<div class="row"><span class="k">取货时间</span><span class="v">2026-08-21 09:00</span></div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">产品回传数量</div>
|
||
<div class="product">
|
||
<div class="product-name">硫酸</div>
|
||
<div class="product-meta">计划 28 吨 · 实际待填</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<div class="btn-row">
|
||
<button class="btn btn-ghost" type="button" data-go="list">运输记录</button>
|
||
<button class="btn btn-primary" type="button">保存回执</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Screen: archived detail (from link or list) -->
|
||
<div class="screen" data-screen="detail-archived">
|
||
<div class="nav">运输计划</div>
|
||
<div class="scroll no-footer-pad">
|
||
<div class="empty">
|
||
<div class="icon">📦</div>
|
||
<div class="title">运输计划已归档完成</div>
|
||
<div>业务详情不可查看</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<button class="btn btn-ghost" type="button" data-go="list">运输记录</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Screen: voided detail -->
|
||
<div class="screen" data-screen="detail-voided">
|
||
<div class="nav">运输计划</div>
|
||
<div class="scroll no-footer-pad">
|
||
<div class="empty">
|
||
<div class="icon">⛔</div>
|
||
<div class="title">运输计划已作废</div>
|
||
<div>业务详情不可查看</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<button class="btn btn-ghost" type="button" data-go="list">运输记录</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Screen: history list -->
|
||
<div class="screen" data-screen="list">
|
||
<div class="nav">
|
||
<button class="back" type="button" data-go="detail-active">‹ 返回</button>
|
||
运输记录
|
||
</div>
|
||
<div class="scroll" style="padding-bottom:16px">
|
||
<div class="filters">
|
||
<div class="filter-row" style="gap:8px">
|
||
<button type="button" class="chip on" data-quick="0">今天</button>
|
||
<button type="button" class="chip" data-quick="1">明天</button>
|
||
<button type="button" class="chip" data-quick="2">后天</button>
|
||
</div>
|
||
<div class="filter-row">
|
||
<input type="date" id="pickDate" value="2026-08-21" style="height:36px;border:1px solid var(--line);border-radius:8px;padding:0 10px;font-size:13px;width:100%;background:#fff" />
|
||
</div>
|
||
<div class="hint" id="filterHint">当前:今天 2026-08-21 · 取货或送货命中</div>
|
||
</div>
|
||
|
||
<div id="recordList"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Static reference: footer always -->
|
||
<div class="phone-wrap">
|
||
<div class="phone-label">对照:任意状态底部都有「运输记录」</div>
|
||
<div class="phone">
|
||
<div class="nav">运输计划</div>
|
||
<div class="scroll no-footer-pad">
|
||
<div class="empty">
|
||
<div class="icon">📦</div>
|
||
<div class="title">运输计划已归档完成</div>
|
||
<div>与现状一致,详情不展示</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<button class="btn btn-ghost" type="button">运输记录</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="note">
|
||
<strong>Demo 说明(已确认部分):</strong><br />
|
||
1)「运输记录」按<strong>主车车牌号</strong>聚合历史与后续安排。<br />
|
||
2)任意状态底部仅保留「运输记录」;不再提供「返回列表」。<br />
|
||
3)筛选:单日日期框 +「今天/明天/后天」快捷;<strong>默认今天</strong>。<br />
|
||
4)命中:取货日或送货日<strong>任一等于</strong>所选日。
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const records = [
|
||
{
|
||
id: "r1",
|
||
planNo: "YS20260821001",
|
||
pickupDate: "2026-08-21",
|
||
deliveryDate: "2026-08-21",
|
||
route: "江宁仓库 → 镇江厂区",
|
||
status: "arranged",
|
||
statusLabel: "进行中",
|
||
tag: "ok",
|
||
screen: "detail-active"
|
||
},
|
||
{
|
||
id: "r2",
|
||
planNo: "YS20260821002",
|
||
pickupDate: "2026-08-21",
|
||
deliveryDate: "2026-08-22",
|
||
route: "仪征码头 → 南京港",
|
||
status: "arranged",
|
||
statusLabel: "进行中",
|
||
tag: "ok",
|
||
screen: "detail-active"
|
||
},
|
||
{
|
||
id: "r3",
|
||
planNo: "YS20260818008",
|
||
pickupDate: "2026-08-18",
|
||
deliveryDate: "2026-08-18",
|
||
route: "徐州仓 → 宿迁站",
|
||
status: "archived",
|
||
statusLabel: "已归档",
|
||
tag: "arch",
|
||
screen: "detail-archived"
|
||
},
|
||
{
|
||
id: "r4",
|
||
planNo: "YS20260815003",
|
||
pickupDate: "2026-08-15",
|
||
deliveryDate: "2026-08-16",
|
||
route: "盐城库 → 南通厂",
|
||
status: "voided",
|
||
statusLabel: "已作废",
|
||
tag: "void",
|
||
screen: "detail-voided"
|
||
},
|
||
{
|
||
id: "r5",
|
||
planNo: "YS20260822001",
|
||
pickupDate: "2026-08-22",
|
||
deliveryDate: "2026-08-22",
|
||
route: "连云港 → 淮安仓",
|
||
status: "arranged",
|
||
statusLabel: "后续安排",
|
||
tag: "warn",
|
||
screen: "detail-active"
|
||
}
|
||
];
|
||
|
||
const todayBase = new Date(2026, 7, 21); // demo fixed "today": 2026-08-21
|
||
|
||
function fmt(d) {
|
||
const y = d.getFullYear();
|
||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||
const day = String(d.getDate()).padStart(2, "0");
|
||
return `${y}-${m}-${day}`;
|
||
}
|
||
|
||
function addDays(base, n) {
|
||
const d = new Date(base.getTime());
|
||
d.setDate(d.getDate() + n);
|
||
return d;
|
||
}
|
||
|
||
const today = fmt(todayBase);
|
||
const phone = document.getElementById("phone");
|
||
const listEl = document.getElementById("recordList");
|
||
const pickDate = document.getElementById("pickDate");
|
||
const filterHint = document.getElementById("filterHint");
|
||
const quickBtns = document.querySelectorAll("[data-quick]");
|
||
|
||
pickDate.value = today;
|
||
|
||
function showScreen(name) {
|
||
phone.querySelectorAll(".screen").forEach((el) => {
|
||
el.classList.toggle("active", el.dataset.screen === name);
|
||
});
|
||
document.querySelectorAll("#jumpTabs button").forEach((btn) => {
|
||
const map = {
|
||
A: "detail-active",
|
||
B: "detail-archived",
|
||
C: "list",
|
||
D: "detail-voided"
|
||
};
|
||
btn.classList.toggle("active", map[btn.dataset.jump] === name);
|
||
});
|
||
}
|
||
|
||
function matchesDate(r, day) {
|
||
return r.pickupDate === day || r.deliveryDate === day;
|
||
}
|
||
|
||
function syncQuickActive(day) {
|
||
quickBtns.forEach((btn) => {
|
||
const offset = Number(btn.dataset.quick);
|
||
const target = fmt(addDays(todayBase, offset));
|
||
btn.classList.toggle("on", target === day);
|
||
});
|
||
}
|
||
|
||
function labelFor(day) {
|
||
if (day === fmt(addDays(todayBase, 0))) return `今天 ${day}`;
|
||
if (day === fmt(addDays(todayBase, 1))) return `明天 ${day}`;
|
||
if (day === fmt(addDays(todayBase, 2))) return `后天 ${day}`;
|
||
return day;
|
||
}
|
||
|
||
function renderList() {
|
||
const day = (pickDate.value || today).trim();
|
||
syncQuickActive(day);
|
||
const filtered = records.filter((r) => matchesDate(r, day));
|
||
filterHint.textContent = `当前:${labelFor(day)} · 取货或送货命中 · 共 ${filtered.length} 条`;
|
||
|
||
if (!filtered.length) {
|
||
listEl.innerHTML = `<div class="empty"><div class="icon">🗂️</div><div class="title">暂无运输记录</div><div>可点「明天/后天」或另选日期</div></div>`;
|
||
return;
|
||
}
|
||
|
||
listEl.innerHTML = filtered
|
||
.map(
|
||
(r) => `
|
||
<div class="record" data-screen="${r.screen}" role="button" tabindex="0">
|
||
<div class="record-top">
|
||
<div class="record-no">${r.planNo}</div>
|
||
<span class="tag ${r.tag}">${r.statusLabel}</span>
|
||
</div>
|
||
<div class="record-meta">
|
||
<div>取货:<strong>${r.pickupDate}</strong> · 送货:<strong>${r.deliveryDate}</strong></div>
|
||
<div>线路:${r.route}</div>
|
||
</div>
|
||
</div>`
|
||
)
|
||
.join("");
|
||
|
||
listEl.querySelectorAll(".record").forEach((el) => {
|
||
el.addEventListener("click", () => showScreen(el.dataset.screen));
|
||
});
|
||
}
|
||
|
||
phone.addEventListener("click", (e) => {
|
||
const btn = e.target.closest("[data-go]");
|
||
if (!btn) return;
|
||
showScreen(btn.dataset.go);
|
||
if (btn.dataset.go === "list") renderList();
|
||
});
|
||
|
||
quickBtns.forEach((btn) => {
|
||
btn.addEventListener("click", () => {
|
||
pickDate.value = fmt(addDays(todayBase, Number(btn.dataset.quick)));
|
||
renderList();
|
||
});
|
||
});
|
||
pickDate.addEventListener("change", renderList);
|
||
|
||
document.getElementById("jumpTabs").addEventListener("click", (e) => {
|
||
const btn = e.target.closest("button[data-jump]");
|
||
if (!btn) return;
|
||
const map = {
|
||
A: "detail-active",
|
||
B: "detail-archived",
|
||
C: "list",
|
||
D: "detail-voided"
|
||
};
|
||
showScreen(map[btn.dataset.jump]);
|
||
if (btn.dataset.jump === "C") renderList();
|
||
});
|
||
|
||
renderList();
|
||
</script>
|
||
</body>
|
||
</html>
|