demo-driver-transport-history.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
  6. <title>司机链接 · 运输记录 Demo</title>
  7. <style>
  8. :root {
  9. --green: #059669;
  10. --green-deep: #047857;
  11. --green-soft: #ecfdf5;
  12. --text: #1e293b;
  13. --muted: #64748b;
  14. --line: #e2e8f0;
  15. --bg: #f1f5f9;
  16. --warn: #d97706;
  17. --danger: #dc2626;
  18. --card: #fff;
  19. --radius: 12px;
  20. --safe-bottom: env(safe-area-inset-bottom, 0px);
  21. }
  22. * { box-sizing: border-box; }
  23. body {
  24. margin: 0;
  25. font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", sans-serif;
  26. background: #0f172a;
  27. color: var(--text);
  28. min-height: 100vh;
  29. }
  30. .stage {
  31. max-width: 1100px;
  32. margin: 0 auto;
  33. padding: 24px 16px 48px;
  34. }
  35. .stage-head {
  36. color: #e2e8f0;
  37. margin-bottom: 20px;
  38. }
  39. .stage-head h1 {
  40. margin: 0 0 8px;
  41. font-size: 22px;
  42. font-weight: 700;
  43. }
  44. .stage-head p {
  45. margin: 0;
  46. color: #94a3b8;
  47. font-size: 14px;
  48. line-height: 1.6;
  49. }
  50. .phones {
  51. display: grid;
  52. grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  53. gap: 20px;
  54. }
  55. .phone-wrap {
  56. background: #1e293b;
  57. border-radius: 20px;
  58. padding: 12px;
  59. border: 1px solid #334155;
  60. }
  61. .phone-label {
  62. color: #cbd5e1;
  63. font-size: 13px;
  64. font-weight: 600;
  65. margin: 0 0 10px 4px;
  66. }
  67. .phone {
  68. width: 100%;
  69. max-width: 390px;
  70. margin: 0 auto;
  71. background: var(--bg);
  72. border-radius: 18px;
  73. overflow: hidden;
  74. min-height: 720px;
  75. display: flex;
  76. flex-direction: column;
  77. box-shadow: 0 20px 40px rgba(0,0,0,.35);
  78. position: relative;
  79. }
  80. .nav {
  81. background: var(--green);
  82. color: #fff;
  83. height: 48px;
  84. display: flex;
  85. align-items: center;
  86. justify-content: center;
  87. font-size: 16px;
  88. font-weight: 600;
  89. position: relative;
  90. flex-shrink: 0;
  91. }
  92. .nav .back {
  93. position: absolute;
  94. left: 12px;
  95. top: 50%;
  96. transform: translateY(-50%);
  97. background: transparent;
  98. border: 0;
  99. color: #fff;
  100. font-size: 14px;
  101. cursor: pointer;
  102. padding: 8px;
  103. }
  104. .scroll {
  105. flex: 1;
  106. overflow: auto;
  107. padding: 12px 12px 96px;
  108. }
  109. .scroll.no-footer-pad { padding-bottom: 12px; }
  110. .card {
  111. background: var(--card);
  112. border-radius: var(--radius);
  113. margin-bottom: 12px;
  114. overflow: hidden;
  115. border: 1px solid #eef2f7;
  116. }
  117. .card-title {
  118. padding: 10px 14px 0;
  119. font-size: 13px;
  120. color: var(--muted);
  121. font-weight: 600;
  122. }
  123. .row {
  124. display: flex;
  125. justify-content: space-between;
  126. gap: 12px;
  127. padding: 12px 14px;
  128. border-bottom: 1px solid #f1f5f9;
  129. font-size: 14px;
  130. }
  131. .row:last-child { border-bottom: 0; }
  132. .row .k { color: var(--muted); flex-shrink: 0; }
  133. .row .v { color: var(--text); text-align: right; word-break: break-all; }
  134. .empty {
  135. padding: 64px 24px;
  136. text-align: center;
  137. color: var(--muted);
  138. }
  139. .empty .icon {
  140. width: 64px;
  141. height: 64px;
  142. margin: 0 auto 16px;
  143. border-radius: 50%;
  144. background: #e2e8f0;
  145. display: grid;
  146. place-items: center;
  147. font-size: 28px;
  148. color: #94a3b8;
  149. }
  150. .empty .title {
  151. font-size: 15px;
  152. color: #475569;
  153. margin-bottom: 6px;
  154. }
  155. .footer {
  156. position: absolute;
  157. left: 0;
  158. right: 0;
  159. bottom: 0;
  160. padding: 10px 12px calc(10px + var(--safe-bottom));
  161. background: rgba(255,255,255,.96);
  162. border-top: 1px solid var(--line);
  163. display: flex;
  164. flex-direction: column;
  165. gap: 8px;
  166. backdrop-filter: blur(8px);
  167. }
  168. .btn {
  169. height: 44px;
  170. border-radius: 999px;
  171. border: 0;
  172. font-size: 15px;
  173. font-weight: 600;
  174. cursor: pointer;
  175. display: inline-flex;
  176. align-items: center;
  177. justify-content: center;
  178. gap: 6px;
  179. }
  180. .btn-primary {
  181. background: var(--green);
  182. color: #fff;
  183. }
  184. .btn-primary:active { background: var(--green-deep); }
  185. .btn-ghost {
  186. background: #fff;
  187. color: var(--green);
  188. border: 1px solid #a7f3d0;
  189. }
  190. .btn-ghost:active { background: var(--green-soft); }
  191. .btn-row {
  192. display: grid;
  193. grid-template-columns: 1fr 1fr;
  194. gap: 8px;
  195. }
  196. .filters {
  197. background: var(--card);
  198. border-radius: var(--radius);
  199. padding: 12px;
  200. margin-bottom: 12px;
  201. border: 1px solid #eef2f7;
  202. }
  203. .filter-row {
  204. display: flex;
  205. align-items: center;
  206. gap: 8px;
  207. margin-bottom: 10px;
  208. flex-wrap: wrap;
  209. }
  210. .filter-row:last-child { margin-bottom: 0; }
  211. .chip {
  212. display: inline-flex;
  213. align-items: center;
  214. gap: 6px;
  215. height: 32px;
  216. padding: 0 12px;
  217. border-radius: 999px;
  218. border: 1px solid var(--line);
  219. background: #fff;
  220. font-size: 13px;
  221. color: var(--text);
  222. cursor: pointer;
  223. user-select: none;
  224. }
  225. .chip.on {
  226. background: var(--green-soft);
  227. border-color: #6ee7b7;
  228. color: var(--green-deep);
  229. font-weight: 600;
  230. }
  231. .chip input { display: none; }
  232. .date-inputs {
  233. display: grid;
  234. grid-template-columns: 1fr auto 1fr;
  235. gap: 8px;
  236. align-items: center;
  237. width: 100%;
  238. }
  239. .date-inputs input {
  240. height: 36px;
  241. border: 1px solid var(--line);
  242. border-radius: 8px;
  243. padding: 0 10px;
  244. font-size: 13px;
  245. color: var(--text);
  246. background: #fff;
  247. width: 100%;
  248. }
  249. .date-inputs span { color: var(--muted); font-size: 12px; }
  250. .hint {
  251. font-size: 12px;
  252. color: var(--muted);
  253. margin-top: 8px;
  254. }
  255. .record {
  256. background: var(--card);
  257. border-radius: var(--radius);
  258. border: 1px solid #eef2f7;
  259. padding: 14px;
  260. margin-bottom: 10px;
  261. cursor: pointer;
  262. transition: border-color .15s, box-shadow .15s;
  263. }
  264. .record:active, .record:hover {
  265. border-color: #6ee7b7;
  266. box-shadow: 0 0 0 3px rgba(16,185,129,.12);
  267. }
  268. .record-top {
  269. display: flex;
  270. justify-content: space-between;
  271. gap: 8px;
  272. align-items: flex-start;
  273. margin-bottom: 8px;
  274. }
  275. .record-no {
  276. font-size: 15px;
  277. font-weight: 700;
  278. }
  279. .tag {
  280. display: inline-flex;
  281. align-items: center;
  282. height: 22px;
  283. padding: 0 8px;
  284. border-radius: 6px;
  285. font-size: 12px;
  286. font-weight: 600;
  287. color: #fff;
  288. flex-shrink: 0;
  289. }
  290. .tag.ok { background: var(--green); }
  291. .tag.arch { background: #64748b; }
  292. .tag.void { background: var(--danger); }
  293. .tag.warn { background: var(--warn); }
  294. .record-meta {
  295. font-size: 13px;
  296. color: var(--muted);
  297. line-height: 1.55;
  298. }
  299. .record-meta strong { color: var(--text); font-weight: 500; }
  300. .mini-tabs {
  301. display: flex;
  302. gap: 8px;
  303. margin-bottom: 12px;
  304. flex-wrap: wrap;
  305. }
  306. .mini-tabs button {
  307. border: 1px solid #334155;
  308. background: #0f172a;
  309. color: #94a3b8;
  310. border-radius: 999px;
  311. padding: 8px 12px;
  312. font-size: 12px;
  313. cursor: pointer;
  314. }
  315. .mini-tabs button.active {
  316. background: var(--green);
  317. border-color: var(--green);
  318. color: #fff;
  319. font-weight: 600;
  320. }
  321. .note {
  322. margin-top: 18px;
  323. padding: 14px 16px;
  324. background: #1e293b;
  325. border: 1px solid #334155;
  326. border-radius: 12px;
  327. color: #cbd5e1;
  328. font-size: 13px;
  329. line-height: 1.7;
  330. }
  331. .note strong { color: #f8fafc; }
  332. .screen { display: none; flex: 1; flex-direction: column; min-height: 0; }
  333. .screen.active { display: flex; }
  334. .product {
  335. padding: 12px 14px;
  336. border-top: 1px solid #f1f5f9;
  337. }
  338. .product-name { font-weight: 600; margin-bottom: 6px; }
  339. .product-meta { font-size: 13px; color: var(--muted); }
  340. </style>
  341. </head>
  342. <body>
  343. <div class="stage">
  344. <div class="stage-head">
  345. <h1>司机链接 ·「运输记录」交互 Demo</h1>
  346. <p>
  347. 列表默认<strong>今天</strong>。筛选为:单日日期框 +「今天 / 明天 / 后天」快捷按钮。
  348. 命中:取货日或送货日等于所选日。按<strong>主车车牌</strong>聚合。
  349. </p>
  350. <div class="mini-tabs" id="jumpTabs">
  351. <button type="button" class="active" data-jump="A">A · 正常详情</button>
  352. <button type="button" data-jump="B">B · 已归档详情</button>
  353. <button type="button" data-jump="C">C · 运输记录列表</button>
  354. <button type="button" data-jump="D">D · 作废详情</button>
  355. </div>
  356. </div>
  357. <div class="phones">
  358. <!-- Phone interactive -->
  359. <div class="phone-wrap">
  360. <div class="phone-label">可交互 Demo(点底部按钮 / 点记录)</div>
  361. <div class="phone" id="phone">
  362. <!-- Screen: active detail -->
  363. <div class="screen active" data-screen="detail-active">
  364. <div class="nav">运输计划</div>
  365. <div class="scroll">
  366. <div class="card">
  367. <div class="row"><span class="k">计划编号</span><span class="v">YS20260821001</span></div>
  368. <div class="row"><span class="k">计划日期</span><span class="v">2026-08-21</span></div>
  369. <div class="row"><span class="k">车牌</span><span class="v">苏A·D12345</span></div>
  370. <div class="row"><span class="k">司机</span><span class="v">张师傅</span></div>
  371. </div>
  372. <div class="card">
  373. <div class="card-title">取货信息</div>
  374. <div class="row"><span class="k">取货地址</span><span class="v">南京市江宁区××仓库</span></div>
  375. <div class="row"><span class="k">取货时间</span><span class="v">2026-08-21 09:00</span></div>
  376. </div>
  377. <div class="card">
  378. <div class="card-title">产品回传数量</div>
  379. <div class="product">
  380. <div class="product-name">硫酸</div>
  381. <div class="product-meta">计划 28 吨 · 实际待填</div>
  382. </div>
  383. </div>
  384. </div>
  385. <div class="footer">
  386. <div class="btn-row">
  387. <button class="btn btn-ghost" type="button" data-go="list">运输记录</button>
  388. <button class="btn btn-primary" type="button">保存回执</button>
  389. </div>
  390. </div>
  391. </div>
  392. <!-- Screen: archived detail (from link or list) -->
  393. <div class="screen" data-screen="detail-archived">
  394. <div class="nav">运输计划</div>
  395. <div class="scroll no-footer-pad">
  396. <div class="empty">
  397. <div class="icon">📦</div>
  398. <div class="title">运输计划已归档完成</div>
  399. <div>业务详情不可查看</div>
  400. </div>
  401. </div>
  402. <div class="footer">
  403. <button class="btn btn-ghost" type="button" data-go="list">运输记录</button>
  404. </div>
  405. </div>
  406. <!-- Screen: voided detail -->
  407. <div class="screen" data-screen="detail-voided">
  408. <div class="nav">运输计划</div>
  409. <div class="scroll no-footer-pad">
  410. <div class="empty">
  411. <div class="icon">⛔</div>
  412. <div class="title">运输计划已作废</div>
  413. <div>业务详情不可查看</div>
  414. </div>
  415. </div>
  416. <div class="footer">
  417. <button class="btn btn-ghost" type="button" data-go="list">运输记录</button>
  418. </div>
  419. </div>
  420. <!-- Screen: history list -->
  421. <div class="screen" data-screen="list">
  422. <div class="nav">
  423. <button class="back" type="button" data-go="detail-active">‹ 返回</button>
  424. 运输记录
  425. </div>
  426. <div class="scroll" style="padding-bottom:16px">
  427. <div class="filters">
  428. <div class="filter-row" style="gap:8px">
  429. <button type="button" class="chip on" data-quick="0">今天</button>
  430. <button type="button" class="chip" data-quick="1">明天</button>
  431. <button type="button" class="chip" data-quick="2">后天</button>
  432. </div>
  433. <div class="filter-row">
  434. <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" />
  435. </div>
  436. <div class="hint" id="filterHint">当前:今天 2026-08-21 · 取货或送货命中</div>
  437. </div>
  438. <div id="recordList"></div>
  439. </div>
  440. </div>
  441. </div>
  442. </div>
  443. <!-- Static reference: footer always -->
  444. <div class="phone-wrap">
  445. <div class="phone-label">对照:任意状态底部都有「运输记录」</div>
  446. <div class="phone">
  447. <div class="nav">运输计划</div>
  448. <div class="scroll no-footer-pad">
  449. <div class="empty">
  450. <div class="icon">📦</div>
  451. <div class="title">运输计划已归档完成</div>
  452. <div>与现状一致,详情不展示</div>
  453. </div>
  454. </div>
  455. <div class="footer">
  456. <button class="btn btn-ghost" type="button">运输记录</button>
  457. </div>
  458. </div>
  459. </div>
  460. </div>
  461. <div class="note">
  462. <strong>Demo 说明(已确认部分):</strong><br />
  463. 1)「运输记录」按<strong>主车车牌号</strong>聚合历史与后续安排。<br />
  464. 2)任意状态底部仅保留「运输记录」;不再提供「返回列表」。<br />
  465. 3)筛选:单日日期框 +「今天/明天/后天」快捷;<strong>默认今天</strong>。<br />
  466. 4)命中:取货日或送货日<strong>任一等于</strong>所选日。
  467. </div>
  468. </div>
  469. <script>
  470. const records = [
  471. {
  472. id: "r1",
  473. planNo: "YS20260821001",
  474. pickupDate: "2026-08-21",
  475. deliveryDate: "2026-08-21",
  476. route: "江宁仓库 → 镇江厂区",
  477. status: "arranged",
  478. statusLabel: "进行中",
  479. tag: "ok",
  480. screen: "detail-active"
  481. },
  482. {
  483. id: "r2",
  484. planNo: "YS20260821002",
  485. pickupDate: "2026-08-21",
  486. deliveryDate: "2026-08-22",
  487. route: "仪征码头 → 南京港",
  488. status: "arranged",
  489. statusLabel: "进行中",
  490. tag: "ok",
  491. screen: "detail-active"
  492. },
  493. {
  494. id: "r3",
  495. planNo: "YS20260818008",
  496. pickupDate: "2026-08-18",
  497. deliveryDate: "2026-08-18",
  498. route: "徐州仓 → 宿迁站",
  499. status: "archived",
  500. statusLabel: "已归档",
  501. tag: "arch",
  502. screen: "detail-archived"
  503. },
  504. {
  505. id: "r4",
  506. planNo: "YS20260815003",
  507. pickupDate: "2026-08-15",
  508. deliveryDate: "2026-08-16",
  509. route: "盐城库 → 南通厂",
  510. status: "voided",
  511. statusLabel: "已作废",
  512. tag: "void",
  513. screen: "detail-voided"
  514. },
  515. {
  516. id: "r5",
  517. planNo: "YS20260822001",
  518. pickupDate: "2026-08-22",
  519. deliveryDate: "2026-08-22",
  520. route: "连云港 → 淮安仓",
  521. status: "arranged",
  522. statusLabel: "后续安排",
  523. tag: "warn",
  524. screen: "detail-active"
  525. }
  526. ];
  527. const todayBase = new Date(2026, 7, 21); // demo fixed "today": 2026-08-21
  528. function fmt(d) {
  529. const y = d.getFullYear();
  530. const m = String(d.getMonth() + 1).padStart(2, "0");
  531. const day = String(d.getDate()).padStart(2, "0");
  532. return `${y}-${m}-${day}`;
  533. }
  534. function addDays(base, n) {
  535. const d = new Date(base.getTime());
  536. d.setDate(d.getDate() + n);
  537. return d;
  538. }
  539. const today = fmt(todayBase);
  540. const phone = document.getElementById("phone");
  541. const listEl = document.getElementById("recordList");
  542. const pickDate = document.getElementById("pickDate");
  543. const filterHint = document.getElementById("filterHint");
  544. const quickBtns = document.querySelectorAll("[data-quick]");
  545. pickDate.value = today;
  546. function showScreen(name) {
  547. phone.querySelectorAll(".screen").forEach((el) => {
  548. el.classList.toggle("active", el.dataset.screen === name);
  549. });
  550. document.querySelectorAll("#jumpTabs button").forEach((btn) => {
  551. const map = {
  552. A: "detail-active",
  553. B: "detail-archived",
  554. C: "list",
  555. D: "detail-voided"
  556. };
  557. btn.classList.toggle("active", map[btn.dataset.jump] === name);
  558. });
  559. }
  560. function matchesDate(r, day) {
  561. return r.pickupDate === day || r.deliveryDate === day;
  562. }
  563. function syncQuickActive(day) {
  564. quickBtns.forEach((btn) => {
  565. const offset = Number(btn.dataset.quick);
  566. const target = fmt(addDays(todayBase, offset));
  567. btn.classList.toggle("on", target === day);
  568. });
  569. }
  570. function labelFor(day) {
  571. if (day === fmt(addDays(todayBase, 0))) return `今天 ${day}`;
  572. if (day === fmt(addDays(todayBase, 1))) return `明天 ${day}`;
  573. if (day === fmt(addDays(todayBase, 2))) return `后天 ${day}`;
  574. return day;
  575. }
  576. function renderList() {
  577. const day = (pickDate.value || today).trim();
  578. syncQuickActive(day);
  579. const filtered = records.filter((r) => matchesDate(r, day));
  580. filterHint.textContent = `当前:${labelFor(day)} · 取货或送货命中 · 共 ${filtered.length} 条`;
  581. if (!filtered.length) {
  582. listEl.innerHTML = `<div class="empty"><div class="icon">🗂️</div><div class="title">暂无运输记录</div><div>可点「明天/后天」或另选日期</div></div>`;
  583. return;
  584. }
  585. listEl.innerHTML = filtered
  586. .map(
  587. (r) => `
  588. <div class="record" data-screen="${r.screen}" role="button" tabindex="0">
  589. <div class="record-top">
  590. <div class="record-no">${r.planNo}</div>
  591. <span class="tag ${r.tag}">${r.statusLabel}</span>
  592. </div>
  593. <div class="record-meta">
  594. <div>取货:<strong>${r.pickupDate}</strong> · 送货:<strong>${r.deliveryDate}</strong></div>
  595. <div>线路:${r.route}</div>
  596. </div>
  597. </div>`
  598. )
  599. .join("");
  600. listEl.querySelectorAll(".record").forEach((el) => {
  601. el.addEventListener("click", () => showScreen(el.dataset.screen));
  602. });
  603. }
  604. phone.addEventListener("click", (e) => {
  605. const btn = e.target.closest("[data-go]");
  606. if (!btn) return;
  607. showScreen(btn.dataset.go);
  608. if (btn.dataset.go === "list") renderList();
  609. });
  610. quickBtns.forEach((btn) => {
  611. btn.addEventListener("click", () => {
  612. pickDate.value = fmt(addDays(todayBase, Number(btn.dataset.quick)));
  613. renderList();
  614. });
  615. });
  616. pickDate.addEventListener("change", renderList);
  617. document.getElementById("jumpTabs").addEventListener("click", (e) => {
  618. const btn = e.target.closest("button[data-jump]");
  619. if (!btn) return;
  620. const map = {
  621. A: "detail-active",
  622. B: "detail-archived",
  623. C: "list",
  624. D: "detail-voided"
  625. };
  626. showScreen(map[btn.dataset.jump]);
  627. if (btn.dataset.jump === "C") renderList();
  628. });
  629. renderList();
  630. </script>
  631. </body>
  632. </html>