فهرست منبع

docs: 入库/出库单现场链接与信息完善设计说明

记录公共页对齐 Demo、管理端列表/弹窗/计划回传补取送货与产品字段的方案 A,供实现引用。

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 ماه پیش
والد
کامیت
9a1e43e371

+ 813 - 0
docs/superpowers/brainstorm/2026-07-19-pl-outbound-site-link-demo.html

@@ -0,0 +1,813 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
+  <title>现场链接 · 出库单公共页 Demo</title>
+  <style>
+    :root {
+      --primary: #059669;
+      --primary-bright: #10b981;
+      --primary-bg: #ecfdf5;
+      --primary-soft: #e6f8ed;
+      --text: #0f172a;
+      --text-secondary: #1e293b;
+      --muted: #64748b;
+      --muted-light: #94a3b8;
+      --border: #e2e8f0;
+      --page-bg: #f1f5f9;
+      --card: #fff;
+      --danger: #ef4444;
+      --warning-bg: #fffbeb;
+      --warning-text: #b45309;
+    }
+
+    * { box-sizing: border-box; margin: 0; padding: 0; }
+    body {
+      font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
+      background: #0f172a;
+      color: var(--text);
+      min-height: 100vh;
+      padding: 24px 16px 48px;
+    }
+
+    .demo-header {
+      max-width: 960px;
+      margin: 0 auto 20px;
+      color: #e2e8f0;
+    }
+    .demo-header h1 {
+      font-size: 20px;
+      font-weight: 700;
+      margin-bottom: 6px;
+    }
+    .demo-header p {
+      font-size: 13px;
+      color: #94a3b8;
+      line-height: 1.5;
+    }
+    .demo-header .tags {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      margin-top: 12px;
+    }
+    .demo-header .tag {
+      font-size: 11px;
+      padding: 4px 10px;
+      border-radius: 999px;
+      background: #1e293b;
+      color: #a7f3d0;
+      border: 1px solid #334155;
+    }
+
+    .tabs {
+      max-width: 960px;
+      margin: 0 auto 16px;
+      display: flex;
+      gap: 8px;
+    }
+    .tabs button {
+      flex: 1;
+      border: 1px solid #334155;
+      background: #1e293b;
+      color: #94a3b8;
+      padding: 10px 12px;
+      border-radius: 10px;
+      font-size: 13px;
+      font-weight: 600;
+      cursor: pointer;
+    }
+    .tabs button.active {
+      background: var(--primary);
+      border-color: var(--primary);
+      color: #fff;
+    }
+
+    .phones {
+      max-width: 960px;
+      margin: 0 auto;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 24px;
+      justify-content: center;
+    }
+    .phone-wrap {
+      display: none;
+      flex-direction: column;
+      align-items: center;
+      gap: 10px;
+    }
+    .phone-wrap.show { display: flex; }
+    .phone-wrap.dual { display: none; }
+    .phones.show-dual .phone-wrap.dual { display: flex; }
+    .phones.show-dual .phone-wrap.single { display: none; }
+
+    .phone-label {
+      font-size: 12px;
+      color: #94a3b8;
+      font-weight: 600;
+    }
+
+    .phone {
+      width: 375px;
+      height: 720px;
+      background: var(--page-bg);
+      border-radius: 28px;
+      border: 3px solid #334155;
+      box-shadow: 0 20px 50px rgba(0,0,0,.45);
+      overflow: hidden;
+      display: flex;
+      flex-direction: column;
+      position: relative;
+    }
+
+    .nav {
+      background: var(--primary);
+      color: #fff;
+      height: 46px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 16px;
+      font-weight: 600;
+      flex-shrink: 0;
+    }
+
+    .scroll {
+      flex: 1;
+      overflow-y: auto;
+      padding: 12px 12px 88px;
+      -webkit-overflow-scrolling: touch;
+    }
+
+    .card {
+      background: var(--card);
+      border-radius: 12px;
+      margin-bottom: 10px;
+      overflow: hidden;
+      box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
+    }
+
+    .card-title {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      padding: 8px 12px 4px;
+      font-size: 12px;
+      font-weight: 600;
+      color: #047857;
+      background: linear-gradient(90deg, #ecfdf5 0%, rgba(236,253,245,0) 72%);
+    }
+    .card-title::before {
+      content: "";
+      width: 3px;
+      height: 12px;
+      border-radius: 2px;
+      background: var(--primary);
+      flex-shrink: 0;
+    }
+
+    .cell {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      gap: 12px;
+      padding: 9px 14px;
+      border-bottom: 1px solid #f1f5f9;
+      font-size: 14px;
+      line-height: 1.35;
+    }
+    .cell:last-child { border-bottom: none; }
+    .cell .label { color: var(--muted); flex-shrink: 0; min-width: 72px; }
+    .cell .value {
+      color: var(--text-secondary);
+      font-weight: 600;
+      text-align: right;
+      word-break: break-all;
+    }
+    .cell.multiline .value { text-align: left; font-weight: 500; }
+
+    .addr-row {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      gap: 6px 8px;
+      justify-content: flex-end;
+    }
+    .addr-text { font-weight: 600; color: var(--text-secondary); text-align: right; }
+    .nav-btn {
+      flex-shrink: 0;
+      height: 22px;
+      padding: 0 8px;
+      font-size: 11px;
+      line-height: 22px;
+      border-radius: 999px;
+      border: none;
+      background: var(--primary);
+      color: #fff;
+      font-weight: 600;
+      cursor: pointer;
+    }
+
+    /* —— 现状:简陋产品数量 —— */
+    .qty-old {
+      padding: 10px 14px 4px;
+    }
+    .qty-old__name {
+      font-weight: 600;
+      color: var(--text);
+      font-size: 14px;
+    }
+    .qty-old__spec {
+      margin-left: 8px;
+      font-weight: 400;
+      color: var(--muted);
+      font-size: 12px;
+    }
+    .qty-old__plan {
+      margin-top: 4px;
+      font-size: 12px;
+      color: var(--muted-light);
+    }
+    .qty-old__field {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 10px 0 12px;
+      border-bottom: 1px solid #f1f5f9;
+      font-size: 14px;
+    }
+    .qty-old__field:last-child { border-bottom: none; }
+    .qty-old__input {
+      width: 120px;
+      height: 32px;
+      border: 1px solid var(--border);
+      border-radius: 6px;
+      padding: 0 10px;
+      text-align: right;
+      font-size: 14px;
+      color: var(--text);
+      background: #f8fafc;
+    }
+
+    /* —— 提案:美化产品实际数量 —— */
+    .product-card {
+      margin: 10px 12px;
+      padding: 14px;
+      border-radius: 12px;
+      background: linear-gradient(180deg, #f0fdf4 0%, #fff 48%);
+      border: 1px solid #d1fae5;
+    }
+    .product-card + .product-card { margin-top: 0; }
+    .product-card__head {
+      display: flex;
+      align-items: flex-start;
+      justify-content: space-between;
+      gap: 8px;
+      margin-bottom: 10px;
+    }
+    .product-card__name {
+      font-size: 15px;
+      font-weight: 700;
+      color: var(--text);
+      line-height: 1.35;
+    }
+    .product-card__badge {
+      flex-shrink: 0;
+      font-size: 10px;
+      font-weight: 600;
+      padding: 2px 8px;
+      border-radius: 999px;
+      background: #d1fae5;
+      color: #047857;
+    }
+    .meta-grid {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 8px;
+      margin-bottom: 12px;
+    }
+    .meta-item {
+      background: rgba(255,255,255,.85);
+      border: 1px solid #e2e8f0;
+      border-radius: 8px;
+      padding: 8px 10px;
+    }
+    .meta-item.span-2 { grid-column: 1 / -1; }
+    .meta-item__label {
+      font-size: 11px;
+      color: var(--muted);
+      margin-bottom: 2px;
+    }
+    .meta-item__value {
+      font-size: 13px;
+      font-weight: 600;
+      color: var(--text-secondary);
+      word-break: break-all;
+    }
+    .meta-item__value.plan {
+      color: var(--primary);
+      font-size: 15px;
+    }
+    .actual-box {
+      background: #fff;
+      border: 1px solid #a7f3d0;
+      border-radius: 10px;
+      padding: 10px 12px;
+    }
+    .actual-box__label {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 8px;
+      font-size: 12px;
+      color: var(--muted);
+    }
+    .actual-box__label strong {
+      color: var(--text-secondary);
+      font-size: 13px;
+    }
+    .actual-box__row {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+    .actual-box__input {
+      flex: 1;
+      height: 40px;
+      border: 1.5px solid #6ee7b7;
+      border-radius: 8px;
+      padding: 0 12px;
+      font-size: 18px;
+      font-weight: 700;
+      color: var(--primary);
+      background: var(--primary-bg);
+      outline: none;
+    }
+    .actual-box__input:focus {
+      border-color: var(--primary);
+      box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
+    }
+    .actual-box__unit {
+      flex-shrink: 0;
+      font-size: 14px;
+      font-weight: 700;
+      color: var(--primary);
+      min-width: 28px;
+    }
+    .actual-hint {
+      margin-top: 6px;
+      font-size: 11px;
+      color: var(--muted-light);
+    }
+
+    .uploader {
+      padding: 12px 14px;
+      display: flex;
+      gap: 10px;
+      flex-wrap: wrap;
+    }
+    .upload-slot {
+      width: 72px;
+      height: 72px;
+      border-radius: 8px;
+      border: 1.5px dashed #a7f3d0;
+      background: var(--primary-soft);
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      color: #168f45;
+      font-size: 11px;
+      gap: 4px;
+    }
+    .upload-slot .plus { font-size: 22px; line-height: 1; font-weight: 300; }
+    .upload-preview {
+      width: 72px;
+      height: 72px;
+      border-radius: 8px;
+      background: linear-gradient(135deg, #d1fae5, #a7f3d0);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 11px;
+      color: #047857;
+      font-weight: 600;
+    }
+
+    .remark-area {
+      padding: 8px 14px 14px;
+    }
+    .remark-area textarea {
+      width: 100%;
+      min-height: 88px;
+      border: 1px solid var(--border);
+      border-radius: 8px;
+      padding: 10px 12px;
+      font-size: 14px;
+      line-height: 1.45;
+      resize: none;
+      color: var(--text);
+      font-family: inherit;
+      background: #f8fafc;
+    }
+
+    .footer {
+      position: absolute;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0));
+      background: rgba(255,255,255,.96);
+      border-top: 1px solid var(--border);
+      backdrop-filter: blur(8px);
+    }
+    .footer button {
+      width: 100%;
+      height: 44px;
+      border: none;
+      border-radius: 999px;
+      background: var(--primary);
+      color: #fff;
+      font-size: 16px;
+      font-weight: 600;
+      cursor: pointer;
+    }
+
+    .diff-note {
+      max-width: 960px;
+      margin: 24px auto 0;
+      background: #1e293b;
+      border: 1px solid #334155;
+      border-radius: 12px;
+      padding: 16px 18px;
+      color: #cbd5e1;
+      font-size: 13px;
+      line-height: 1.55;
+    }
+    .diff-note h2 {
+      font-size: 14px;
+      color: #a7f3d0;
+      margin-bottom: 10px;
+    }
+    .diff-note ul { padding-left: 18px; }
+    .diff-note li { margin-bottom: 6px; }
+    .diff-note code {
+      background: #0f172a;
+      padding: 1px 6px;
+      border-radius: 4px;
+      font-size: 12px;
+      color: #fde68a;
+    }
+
+    @media (max-width: 420px) {
+      .phone { width: 100%; max-width: 375px; height: 680px; }
+    }
+  </style>
+</head>
+<body>
+  <div class="demo-header">
+    <h1>现场链接 · 出库单公共页 Demo</h1>
+    <p>
+      对照「旧版运输现场」与「当前出库单现场链接」差异,提案页补回取送货信息,并美化「产品实际数量」
+     (单位显示「吨」、展示包装规格)。可在浏览器直接打开本文件预览。
+    </p>
+    <div class="tags">
+      <span class="tag">路由 /#/public/pl/outbound/:token</span>
+      <span class="tag">对齐旧版 site 信息密度</span>
+      <span class="tag">保留新版:填实际数量 + 回执</span>
+    </div>
+  </div>
+
+  <div class="tabs">
+    <button type="button" class="active" data-tab="proposed">提案(完善后)</button>
+    <button type="button" data-tab="current">现状(数据少)</button>
+    <button type="button" data-tab="compare">左右对照</button>
+  </div>
+
+  <div class="phones" id="phones">
+    <!-- 提案:完善后 -->
+    <div class="phone-wrap single show" data-panel="proposed">
+      <div class="phone-label">提案 · 完善后的现场链接</div>
+      <div class="phone">
+        <div class="nav">出库单 · 现场回执</div>
+        <div class="scroll">
+          <div class="card">
+            <div class="cell"><span class="label">业务类型</span><span class="value">出库</span></div>
+            <div class="cell"><span class="label">日期</span><span class="value">2026-07-19</span></div>
+            <div class="cell"><span class="label">往来单位</span><span class="value">安徽某某化工有限公司</span></div>
+            <div class="cell"><span class="label">车牌</span><span class="value">皖H·A1234</span></div>
+            <div class="cell"><span class="label">计划号</span><span class="value">YS20260719001</span></div>
+            <div class="cell"><span class="label">单号</span><span class="value">CK20260719003</span></div>
+            <div class="cell"><span class="label">状态</span><span class="value">进行中</span></div>
+            <div class="cell multiline"><span class="label">备注</span><span class="value">请按计划数量装车,现场核对包装规格。</span></div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">取货信息</div>
+            <div class="cell multiline">
+              <span class="label">取货地址</span>
+              <span class="value">
+                <span class="addr-row">
+                  <span class="addr-text">安庆市迎江区某某路 88 号仓库</span>
+                  <button type="button" class="nav-btn">导航</button>
+                </span>
+              </span>
+            </div>
+            <div class="cell"><span class="label">取货时间</span><span class="value">2026-07-19 09:30</span></div>
+            <div class="cell multiline"><span class="label">注意事项</span><span class="value">需佩戴安全帽,进厂登记。</span></div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">送货信息</div>
+            <div class="cell multiline">
+              <span class="label">送货地址</span>
+              <span class="value">
+                <span class="addr-row">
+                  <span class="addr-text">合肥市包河区某某工业园 3 栋</span>
+                  <button type="button" class="nav-btn">导航</button>
+                </span>
+              </span>
+            </div>
+            <div class="cell"><span class="label">送达时间</span><span class="value">2026-07-19 15:00</span></div>
+            <div class="cell multiline"><span class="label">注意事项</span><span class="value">卸货区靠北门,联系仓库张工。</span></div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">产品实际数量</div>
+
+            <div class="product-card">
+              <div class="product-card__head">
+                <div class="product-card__name">工业级尿素</div>
+                <span class="product-card__badge">产品 1/2</span>
+              </div>
+              <div class="meta-grid">
+                <div class="meta-item">
+                  <div class="meta-item__label">规格型号</div>
+                  <div class="meta-item__value">46.4%</div>
+                </div>
+                <div class="meta-item">
+                  <div class="meta-item__label">包装规格</div>
+                  <div class="meta-item__value">50kg/袋</div>
+                </div>
+                <div class="meta-item span-2">
+                  <div class="meta-item__label">计划数量</div>
+                  <div class="meta-item__value plan">32.000 吨</div>
+                </div>
+              </div>
+              <div class="actual-box">
+                <div class="actual-box__label">
+                  <strong>实际数量</strong>
+                  <span>请按现场实装填写</span>
+                </div>
+                <div class="actual-box__row">
+                  <input class="actual-box__input" type="number" value="32" step="0.001" />
+                  <span class="actual-box__unit">吨</span>
+                </div>
+                <div class="actual-hint">单位已按字典转中文(dun → 吨),勿直接展示字典码</div>
+              </div>
+            </div>
+
+            <div class="product-card">
+              <div class="product-card__head">
+                <div class="product-card__name">片碱</div>
+                <span class="product-card__badge">产品 2/2</span>
+              </div>
+              <div class="meta-grid">
+                <div class="meta-item">
+                  <div class="meta-item__label">规格型号</div>
+                  <div class="meta-item__value">99%</div>
+                </div>
+                <div class="meta-item">
+                  <div class="meta-item__label">包装规格</div>
+                  <div class="meta-item__value">25kg/袋</div>
+                </div>
+                <div class="meta-item span-2">
+                  <div class="meta-item__label">计划数量</div>
+                  <div class="meta-item__value plan">8.500 吨</div>
+                </div>
+              </div>
+              <div class="actual-box">
+                <div class="actual-box__label">
+                  <strong>实际数量</strong>
+                  <span>请按现场实装填写</span>
+                </div>
+                <div class="actual-box__row">
+                  <input class="actual-box__input" type="number" value="8.5" step="0.001" />
+                  <span class="actual-box__unit">吨</span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">回执上传</div>
+            <div class="uploader">
+              <div class="upload-preview">磅单.jpg</div>
+              <div class="upload-slot"><span class="plus">+</span>上传</div>
+            </div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">现场备注</div>
+            <div class="remark-area">
+              <textarea placeholder="填写现场备注">装车正常,包装完好。</textarea>
+            </div>
+          </div>
+
+          <div class="card">
+            <div class="cell"><span class="label">上次保存</span><span class="value">2026-07-19 10:21</span></div>
+          </div>
+        </div>
+        <div class="footer">
+          <button type="button">提交</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 现状 -->
+    <div class="phone-wrap single" data-panel="current">
+      <div class="phone-label">现状 · 数据偏少</div>
+      <div class="phone">
+        <div class="nav">出库单</div>
+        <div class="scroll">
+          <div class="card">
+            <div class="cell"><span class="label">单号</span><span class="value">CK20260719003</span></div>
+            <div class="cell"><span class="label">状态</span><span class="value">进行中</span></div>
+            <div class="cell"><span class="label">往来单位</span><span class="value">安徽某某化工有限公司</span></div>
+            <div class="cell"><span class="label">车牌</span><span class="value">皖H·A1234</span></div>
+            <div class="cell"><span class="label">计划号</span><span class="value">YS20260719001</span></div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">产品实际数量</div>
+            <div class="qty-old">
+              <div class="qty-old__name">
+                工业级尿素
+                <span class="qty-old__spec">46.4%</span>
+              </div>
+              <div class="qty-old__plan">计划 32 dun</div>
+              <div class="qty-old__field">
+                <span class="label">实际数量</span>
+                <input class="qty-old__input" type="number" value="32" />
+              </div>
+            </div>
+            <div class="qty-old">
+              <div class="qty-old__name">
+                片碱
+                <span class="qty-old__spec">99%</span>
+              </div>
+              <div class="qty-old__plan">计划 8.5 dun</div>
+              <div class="qty-old__field">
+                <span class="label">实际数量</span>
+                <input class="qty-old__input" type="number" value="8.5" />
+              </div>
+            </div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">回执上传</div>
+            <div class="uploader">
+              <div class="upload-slot"><span class="plus">+</span>上传</div>
+            </div>
+          </div>
+
+          <div class="card">
+            <div class="card-title">现场备注</div>
+            <div class="remark-area">
+              <textarea placeholder="填写现场备注"></textarea>
+            </div>
+          </div>
+        </div>
+        <div class="footer">
+          <button type="button">提交</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 对照:现状 -->
+    <div class="phone-wrap dual" data-panel="compare-left">
+      <div class="phone-label">左 · 现状</div>
+      <div class="phone">
+        <div class="nav">出库单</div>
+        <div class="scroll">
+          <div class="card">
+            <div class="cell"><span class="label">单号</span><span class="value">CK20260719003</span></div>
+            <div class="cell"><span class="label">状态</span><span class="value">进行中</span></div>
+            <div class="cell"><span class="label">往来单位</span><span class="value">安徽某某化工…</span></div>
+            <div class="cell"><span class="label">车牌</span><span class="value">皖H·A1234</span></div>
+            <div class="cell"><span class="label">计划号</span><span class="value">YS20260719001</span></div>
+          </div>
+          <div class="card">
+            <div class="card-title">产品实际数量</div>
+            <div class="qty-old">
+              <div class="qty-old__name">工业级尿素 <span class="qty-old__spec">46.4%</span></div>
+              <div class="qty-old__plan">计划 32 dun ← 字典码未转义</div>
+              <div class="qty-old__field">
+                <span class="label">实际数量</span>
+                <input class="qty-old__input" type="number" value="32" />
+              </div>
+            </div>
+          </div>
+          <div class="card">
+            <div class="card-title">回执 / 备注</div>
+            <div class="remark-area"><textarea placeholder="无取送货、无包装规格…"></textarea></div>
+          </div>
+        </div>
+        <div class="footer"><button type="button">提交</button></div>
+      </div>
+    </div>
+
+    <!-- 对照:提案 -->
+    <div class="phone-wrap dual" data-panel="compare-right">
+      <div class="phone-label">右 · 提案</div>
+      <div class="phone">
+        <div class="nav">出库单 · 现场回执</div>
+        <div class="scroll">
+          <div class="card">
+            <div class="cell"><span class="label">业务类型</span><span class="value">出库</span></div>
+            <div class="cell"><span class="label">日期</span><span class="value">2026-07-19</span></div>
+            <div class="cell"><span class="label">往来单位</span><span class="value">安徽某某化工…</span></div>
+            <div class="cell"><span class="label">车牌</span><span class="value">皖H·A1234</span></div>
+            <div class="cell"><span class="label">计划号</span><span class="value">YS20260719001</span></div>
+          </div>
+          <div class="card">
+            <div class="card-title">取货 / 送货</div>
+            <div class="cell"><span class="label">取货</span><span class="value">迎江区某某路…</span></div>
+            <div class="cell"><span class="label">送货</span><span class="value">包河区工业园…</span></div>
+          </div>
+          <div class="card">
+            <div class="card-title">产品实际数量</div>
+            <div class="product-card">
+              <div class="product-card__head">
+                <div class="product-card__name">工业级尿素</div>
+                <span class="product-card__badge">1/2</span>
+              </div>
+              <div class="meta-grid">
+                <div class="meta-item">
+                  <div class="meta-item__label">规格</div>
+                  <div class="meta-item__value">46.4%</div>
+                </div>
+                <div class="meta-item">
+                  <div class="meta-item__label">包装规格</div>
+                  <div class="meta-item__value">50kg/袋</div>
+                </div>
+                <div class="meta-item span-2">
+                  <div class="meta-item__label">计划数量</div>
+                  <div class="meta-item__value plan">32.000 吨</div>
+                </div>
+              </div>
+              <div class="actual-box">
+                <div class="actual-box__label"><strong>实际数量</strong></div>
+                <div class="actual-box__row">
+                  <input class="actual-box__input" type="number" value="32" />
+                  <span class="actual-box__unit">吨</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="footer"><button type="button">提交</button></div>
+      </div>
+    </div>
+  </div>
+
+  <div class="diff-note">
+    <h2>本 Demo 相对现状的改动要点</h2>
+    <ul>
+      <li><strong>补回旧版现场链接信息:</strong>业务类型、日期、备注、取货信息(地址+导航+时间+注意事项)、送货信息、上次保存。</li>
+      <li><strong>保留新版能力:</strong>产品行可填实际数量、整单回执上传、现场备注、提交。</li>
+      <li><strong>产品实际数量美化:</strong>每产品独立卡片;规格 / 包装规格 / 计划数量网格展示;实际数量大字输入 + 单位后缀。</li>
+      <li><strong>单位:</strong>展示字典标签「吨」,不展示字典码 <code>dun</code>(后端公共接口应对齐旧版 <code>crm_product_unit</code> 转义)。</li>
+      <li><strong>包装规格:</strong>明细需带 <code>packageSpec</code>(当前出入库明细表可能缺该字段,需快照补齐或从计划产品回填)。</li>
+    </ul>
+  </div>
+
+  <script>
+    const phones = document.getElementById('phones')
+    const buttons = document.querySelectorAll('.tabs button')
+    const singles = document.querySelectorAll('.phone-wrap.single')
+    const duals = document.querySelectorAll('.phone-wrap.dual')
+
+    buttons.forEach((btn) => {
+      btn.addEventListener('click', () => {
+        buttons.forEach((b) => b.classList.remove('active'))
+        btn.classList.add('active')
+        const tab = btn.dataset.tab
+
+        if (tab === 'compare') {
+          phones.classList.add('show-dual')
+          singles.forEach((el) => el.classList.remove('show'))
+          return
+        }
+
+        phones.classList.remove('show-dual')
+        singles.forEach((el) => {
+          el.classList.toggle('show', el.dataset.panel === tab)
+        })
+      })
+    })
+  </script>
+</body>
+</html>

+ 153 - 0
docs/superpowers/specs/2026-07-19-pl-io-order-site-link-enrichment-design.md

@@ -0,0 +1,153 @@
+# 入库/出库单信息完善与现场链接对齐设计
+
+**日期:** 2026-07-19  
+**状态:** 已确认  
+**范围:** web-admin 公共现场页 + 管理端入库/出库单(列表 / 编辑弹窗 / 计划查看回传)+ backend PL IO 公共与管理 API  
+**视觉参考:** `docs/superpowers/brainstorm/2026-07-19-pl-outbound-site-link-demo.html`「提案(完善后)」  
+**界面模式:** 列表 P01;编辑弹窗 Fresh Mint `dialog-form-layout`;公共页对齐既有 `public/pl/site`
+
+---
+
+## 1. 背景与问题
+
+出入库单上线后,现场链接与管理端展示均为「最小快照」,相对旧版「运输现场」公共页信息明显偏少:
+
+| 场景 | 现状缺口 |
+|------|----------|
+| 现场链接 `/#/public/pl/inbound\|outbound/:token` | 无取/送货、无包装规格;单位展示字典码 `dun`;产品区样式简陋 |
+| 管理端入库/出库列表与编辑弹窗 | 无取/送货;产品仅名称/规格/计划/实际 |
+| 计划查看「入库/出库回传」 | 同上,字段不齐 |
+
+用户确认范围 **C**:公共页按 Demo 完善 + 管理端列表 / 编辑弹窗 / 计划查看回传一并补齐。
+
+---
+
+## 2. 已确认决策
+
+| # | 决策 | 选择 |
+|---|------|------|
+| D1 | 数据组装策略 | **读时 enrichment**(方案 A):从 `pl_plan` / `pl_plan_product` 组装;本期 **不做** 明细表 DDL 快照列 |
+| D2 | 入库 / 出库 | **对称**;公共页共用 `PublicIoOrderPage`;管理端共用 `IoOrderListPage` |
+| D3 | 管理端表面 | **C**:列表 + 编辑弹窗 + `PlPlanViewTabs` 回传区 |
+| D4 | 单位展示 | 公共页与管理端展示 `crm_product_unit` **标签**(`dun` → `吨`);库内仍存字典码 |
+| D5 | 列表产品列 | 列表保持 `productSummary` 摘要;**不**把 8 个产品字段全铺进列表 |
+| D6 | 列表物流列 | 增加取货地址、送货地址(或送货点名称优先)摘要列 |
+| D7 | 可编辑字段 | 仅 `actualQty`、现场备注、附件、状态(既有规则);取送货与产品扩展字段 **只读** |
+
+---
+
+## 3. 后端
+
+### 3.1 Enrichment 输出字段
+
+在 `order_header_to_dict` / 公共 `_build_page` / 管理端 get·list 响应中统一附加(可抽共享 helper):
+
+**主单(来自 `pl_plan`,无计划则空)**
+
+| 字段(camelCase) | 来源 |
+|-------------------|------|
+| `businessType` | 计划业务类型展示名或既有枚举文案 |
+| `remark` | `plan.remark`(计划备注;与现场备注区分) |
+| `pickupAddress` / `pickupLng` / `pickupLat` / `pickupTime` / `pickupNotes` | 计划取货 |
+| `destinationName` / `destinationAddress` / `destinationLng` / `destinationLat` / `deliveryTime` / `destinationNotes` | 计划送货 |
+| `planDate` | 已有快照,保留 |
+
+列表可只保证:`pickupAddress`、`destinationAddress`(展示用:`destinationAddress \|\| destinationName`)。
+
+**明细(来自 `pl_plan_product` via `plan_product_id`)**
+
+| 字段 | 说明 |
+|------|------|
+| `productNo` | 产品编号 |
+| `productName` | 已有 |
+| `specification` | 已有(型号/规格,来自 `model`) |
+| `unit` | 库内码;管理端可用字典转标签,或额外 `unitLabel` |
+| `unitLabel` | 字典标签,缺省回退码或「吨」 |
+| `planQty` / `actualQty` | 已有 |
+| `packageSpec` | 包装规格 |
+| `remark` | 产品行备注 |
+
+公共页:响应中 `unit` 直接返回 **已转义标签**(对齐旧 `public_site._resolve_unit_label`),避免免登录页拉字典。
+
+### 3.2 改动文件(示意)
+
+- `backend/app/services/pl_io_order_service.py` — enrichment + `order_header_to_dict` items 扩展  
+- `backend/app/api/pl/public_io_order.py` — `_build_page` 补全取送货与单位转义(可复用 service helper)  
+- `backend/app/api/pl/io_order.py` — 详情/列表走同一序列化  
+- 可选:schema `IoOrderItemRead` / 前端类型同步  
+
+### 3.3 非目标
+
+- `pl_inbound_order_item` / `pl_outbound_order_item` 加 `package_spec` 等列  
+- 改旧 `/public/pl/site` 历史链路(除非后续单独需求)  
+- 司机运输公共页  
+
+---
+
+## 4. 前端 · 现场公共页
+
+**文件:** `web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue`、`api/pl/public-io-order.ts`  
+**样式参考:** Demo + 既有 `public/pl/site`、`public-mobile-vant.scss`
+
+区块顺序:
+
+1. 概况:业务类型、日期、往来单位、车牌、计划号、单号、状态、计划备注(有则显示)  
+2. 取货信息:地址 + 导航、时间、注意事项  
+3. 送货信息:地址 + 导航、时间、注意事项  
+4. 产品实际数量:每产品卡片 — 名称、规格型号、包装规格、计划数量+单位标签、实际数量输入  
+5. 回执上传、现场备注、上次保存  
+6. 底部提交  
+
+入库/出库仅 `navTitle` / 业务类型文案不同。
+
+---
+
+## 5. 前端 · 管理端列表(`IoOrderListPage`)
+
+在现有列后增加:
+
+- `取货地址` ← `pickupAddress`  
+- `送货地址` ← `destinationAddress \|\| destinationName`  
+
+均 `min-width` + `show-overflow-tooltip`。其余列不变。
+
+---
+
+## 6. 前端 · 编辑弹窗(`IoOrderListPage`)
+
+- 宽度约 **960–1080px**,`dialog-form-layout`,`label-position="top"`,`form-grid-2`  
+- 主信息只读区增加取货/送货字段(长地址 `form-col-span-2`)  
+- 产品表列(固定顺序):
+
+| 产品编号 | 产品名称 | 型号/规格 | 单位 | 计划数量 | 实际数量 | 包装规格 | 备注 |
+
+- 实际数量:`el-input-number`,作废不可编辑  
+- 单位列展示 `unitLabel`(或字典 `getLabel`)  
+
+---
+
+## 7. 前端 · 计划查看回传(`PlPlanViewTabs`)
+
+- `ioOrderViewFields` 补取送货相关 KV  
+- 只读子表与快捷编辑弹窗产品表列与第 6 节对齐  
+
+---
+
+## 8. 验收要点
+
+| ID | 场景 |
+|----|------|
+| V1 | 出库现场链接:有取/送货、导航;产品卡片含包装规格;计划数量单位为「吨」非 `dun` |
+| V2 | 入库现场链接与出库对称 |
+| V3 | 入库/出库列表可见取货、送货地址列 |
+| V4 | 编辑弹窗主信息含取送货;产品 8 列齐全;实际数量可保存 |
+| V5 | 计划查看回传区字段与弹窗对齐 |
+| V6 | 已作废单不可提交现场 / 管理端不可改数量(既有行为保持) |
+
+---
+
+## 9. 决策记录
+
+- 视觉 Demo:`docs/superpowers/brainstorm/2026-07-19-pl-outbound-site-link-demo.html`  
+- 管理端表面:用户选 **C**  
+- 组装策略:用户确认方案 **A**(读时 enrichment)