docs: add pound QR public link and driver-page entry design

Capture the approved approach for sharing weighbridge slip QR via
public inventory-id URLs, multi-line copy text, and a third driver footer button.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-13 09:17:12 +08:00
1 parent df8c28bd7c
commit 157ad5e29a
2 files changed
+786

No files matched your search

@@ -0,0 +1,599 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>磅单二维码 · 独立模板 &amp; 司机页 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--border: #d9e8e2;
--text: #1e293b;
--muted: #64748b;
--page: #f1f5f9;
--van-primary: #07c160;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
background: var(--page);
color: var(--text);
line-height: 1.5;
padding: 24px;
}
h1 { font-size: 22px; margin: 0 0 6px; }
.sub { color: var(--muted); font-size: 14px; margin-bottom: 20px; }
.tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.tab {
border: 1px solid var(--border);
background: #fff;
border-radius: 8px;
padding: 8px 14px;
font-size: 13px;
cursor: pointer;
color: var(--text);
}
.tab.active {
background: var(--primary-bg);
border-color: var(--primary);
color: var(--primary-dark);
font-weight: 600;
}
.panel {
display: none;
background: #fff;
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
margin-bottom: 20px;
}
.panel.active { display: block; }
.panel-head {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: 14px;
}
.panel-body { padding: 16px; }
.hint {
font-size: 12px;
color: var(--muted);
margin: 0 0 12px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 900px) {
.grid-2 { grid-template-columns: 1fr; }
}
/* ===== 复制给司机文本 ===== */
.copy-box {
background: #0f172a;
color: #e2e8f0;
border-radius: 10px;
padding: 14px 16px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12.5px;
white-space: pre-wrap;
line-height: 1.55;
}
.copy-box .hl {
color: #6ee7b7;
font-weight: 600;
}
/* ===== 磅单二维码卡片(对齐 DbPoundSlipQrcodeDialog) ===== */
.qr-stage {
background: rgba(0, 0, 0, 0.92);
border-radius: 12px;
padding: 18px 14px 22px;
max-width: 400px;
margin: 0 auto;
}
.qr-dialog-title {
color: #fff;
font-size: 20px;
font-weight: 700;
text-align: center;
margin: 0 0 10px;
padding: 2px 10px 0;
}
.pound-slip-capture-inline {
background: #f5f7fa;
padding: 4px 8px;
margin: 6px 10px;
border-radius: 8px;
}
.capture-card {
background: #fff;
border-radius: 8px;
margin: 10px;
padding: 6px 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.capture-row {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 4px;
font-size: 13px;
}
.capture-row-large {
font-size: 15px;
font-weight: 600;
}
.capture-row .label {
color: var(--text);
min-width: 82px;
}
.capture-row .value {
color: var(--text);
flex: 1;
}
.capture-row-plate {
width: 100%;
justify-content: stretch;
align-items: center;
margin-bottom: 8px;
}
.vehicle-plate {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 10px 32px;
background: #f5d000;
border: 3px solid #fff;
border-radius: 8px;
box-shadow: 0 0 0 2px #fff;
}
.vehicle-plate-text {
font-size: 32px;
font-weight: 700;
color: #000;
letter-spacing: 1px;
}
.vehicle-plate-dot {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
background: #fff;
}
.vehicle-plate-dot-lt { left: 6px; top: 8px; }
.vehicle-plate-dot-lb { left: 6px; bottom: 8px; }
.vehicle-plate-dot-rt { right: 6px; top: 8px; }
.vehicle-plate-dot-rb { right: 6px; bottom: 8px; }
.capture-qrcode { text-align: center; }
.capture-id {
font-size: 18px;
font-weight: 700;
color: var(--text);
margin-bottom: 6px;
}
.capture-qr-wrap {
padding: 8px 0;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.capture-qr-img {
width: 200px;
height: 200px;
display: inline-block;
background: #fff;
image-rendering: pixelated;
}
.capture-desc {
font-size: 9px;
font-weight: 500;
color: var(--muted);
margin-top: 4px;
padding-bottom: 4px;
}
.url-chip {
margin-top: 12px;
font-size: 12px;
color: #cbd5e1;
text-align: center;
word-break: break-all;
padding: 0 8px;
}
.url-chip code {
color: #6ee7b7;
font-size: 11px;
}
/* ===== 司机手机页 mock ===== */
.phone {
width: 375px;
max-width: 100%;
margin: 0 auto;
background: #f7f8fa;
border: 1px solid #d1d5db;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.12);
position: relative;
min-height: 640px;
display: flex;
flex-direction: column;
}
.phone-status {
height: 28px;
background: linear-gradient(135deg, #0a9f6d, #087f57);
color: #fff;
font-size: 11px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
}
.phone-header {
background: linear-gradient(135deg, #0a9f6d, #087f57);
color: #fff;
padding: 12px 16px 20px;
border-radius: 0 0 20px 20px;
}
.phone-header h2 {
margin: 0;
font-size: 18px;
font-weight: 700;
}
.phone-header p {
margin: 4px 0 0;
font-size: 12px;
opacity: 0.9;
}
.phone-body {
flex: 1;
padding: 12px 12px 88px;
overflow: auto;
}
.card {
background: #fff;
border-radius: 12px;
padding: 12px 14px;
margin-bottom: 10px;
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
}
.card-title {
font-size: 13px;
font-weight: 600;
color: var(--muted);
margin-bottom: 8px;
}
.row {
display: flex;
justify-content: space-between;
gap: 8px;
font-size: 14px;
padding: 4px 0;
}
.row .k { color: var(--muted); }
.row .v { color: var(--text); font-weight: 500; text-align: right; }
.phone-footer {
position: absolute;
left: 0; right: 0; bottom: 0;
background: #fff;
border-top: 1px solid #ebedf0;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
}
.btn-row {
display: flex;
gap: 8px;
}
.btn {
flex: 1;
height: 40px;
border: none;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.btn-ghost {
background: #e8f8f0;
color: var(--van-primary);
}
.btn-main {
background: var(--van-primary);
color: #fff;
}
.btn-qr {
background: #fff7e8;
color: #b45309;
border: 1px solid #f5d78e;
}
.btn-row.three .btn { font-size: 13px; padding: 0 4px; }
/* overlay popup inside phone */
.overlay {
display: none;
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.92);
z-index: 20;
overflow: auto;
padding: 40px 12px 24px;
}
.overlay.show { display: block; }
.overlay .close-x {
position: absolute;
top: 12px;
right: 14px;
width: 32px;
height: 32px;
border-radius: 50%;
border: none;
background: rgba(255,255,255,0.15);
color: #fff;
font-size: 18px;
cursor: pointer;
}
.badge {
display: inline-block;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: var(--primary-bg);
color: var(--primary-dark);
font-weight: 600;
margin-left: 6px;
}
.legend {
display: flex;
gap: 16px;
flex-wrap: wrap;
font-size: 12px;
color: var(--muted);
margin-top: 10px;
}
.legend span::before {
content: '';
display: inline-block;
width: 10px;
height: 10px;
border-radius: 2px;
margin-right: 6px;
vertical-align: -1px;
}
.legend .new::before { background: #fbbf24; }
.legend .exist::before { background: var(--van-primary); }
</style>
</head>
<body>
<h1>磅单二维码 · 独立公共模板 &amp; 司机页 Demo</h1>
<p class="sub">
对齐现有「复制磅单二维码」弹窗样式(黄牌 + 往来单位/物料 + 出入库单 ID 二维码)。
本页演示:① 独立 URL 模板页 ② 司机填写页底部新增按钮 + 弹窗 ③ 复制给司机文案追加链接。
</p>
<div class="tabs">
<button class="tab active" data-tab="tpl" type="button">1. 独立模板页</button>
<button class="tab" data-tab="driver" type="button">2. 司机页 + 弹窗</button>
<button class="tab" data-tab="copy" type="button">3. 复制给司机文案</button>
</div>
<!-- Tab 1: Independent template -->
<section id="panel-tpl" class="panel active">
<div class="panel-head">
独立公共页
<span class="badge">样式与管理端弹窗一致</span>
</div>
<div class="panel-body">
<p class="hint">
拟议路由示例:
<code>#/public/pl/pound-qrcode/:inventoryId</code>
(或 token 版,待需求确认)。传入出入库单 id 后渲染下方界面。
</p>
<div class="qr-stage">
<div class="qr-dialog-title">出库磅单二维码</div>
<div class="pound-slip-capture-inline">
<div class="capture-card">
<div class="capture-row capture-row-plate">
<div class="vehicle-plate">
<span class="vehicle-plate-dot vehicle-plate-dot-lt"></span>
<span class="vehicle-plate-dot vehicle-plate-dot-lb"></span>
<span class="vehicle-plate-text">皖AM8516</span>
<span class="vehicle-plate-dot vehicle-plate-dot-rt"></span>
<span class="vehicle-plate-dot vehicle-plate-dot-rb"></span>
</div>
</div>
<div class="capture-row capture-row-large">
<span class="label">往来单位:</span>
<span class="value">合肥德泽化工科技有限公司</span>
</div>
<div class="capture-row capture-row-large">
<span class="label">物料名称:</span>
<span class="value">30% 工业硫酸(30%)(10.000 吨)</span>
</div>
</div>
<div class="capture-card capture-qrcode">
<div class="capture-id">2026-09-12 出库单ID:a1b2c3d4-e5f6-7890</div>
<div class="capture-qr-wrap">
<img
class="capture-qr-img"
alt="二维码"
src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&amp;data=a1b2c3d4-e5f6-7890"
/>
</div>
<div class="capture-desc">出入库单标识(扫码核验)</div>
</div>
</div>
<div class="url-chip">
链接示例:<br />
<code>http://host/#/public/pl/pound-qrcode/a1b2c3d4-e5f6-7890</code>
</div>
</div>
</div>
</section>
<!-- Tab 2: Driver page -->
<section id="panel-driver" class="panel">
<div class="panel-head">
司机链接填写页底部
<span class="badge">新增「磅单二维码」</span>
</div>
<div class="panel-body">
<p class="hint">
当前底栏为「运输记录 / 保存回执」。本 Demo 增加第三颗「磅单二维码」;点击后深色遮罩弹层,内容与上方模板同款。
(若你口头说的「现场记录」指现场人员页,可另开;本 Demo 按司机页 <code>/public/pl/transport/:token</code>。)
</p>
<div class="phone" id="driverPhone">
<div class="phone-status"><span>9:05</span><span>LTE</span></div>
<div class="phone-header">
<h2>皖AM8516 · 运输计划</h2>
<p>PO20260912008 · 2026-09-12</p>
</div>
<div class="phone-body">
<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">30% 工业硫酸,10 吨</span></div>
</div>
<div class="card">
<div class="card-title">送货 #1</div>
<div class="row"><span class="k">单位</span><span class="v">合肥德泽化工科技有限公司</span></div>
<div class="row"><span class="k">产品</span><span class="v">30% 工业硫酸,10 吨</span></div>
</div>
<div class="card">
<div class="card-title">备注</div>
<div class="row"><span class="k">填写运输备注…</span></div>
</div>
</div>
<div class="phone-footer">
<div class="btn-row three">
<button class="btn btn-ghost" type="button">运输记录</button>
<button class="btn btn-qr" type="button" onclick="openQrOverlay()">磅单二维码</button>
<button class="btn btn-main" type="button">保存回执</button>
</div>
<div class="legend">
<span class="exist">已有按钮</span>
<span class="new">本次新增</span>
</div>
</div>
<div class="overlay" id="qrOverlay">
<button class="close-x" type="button" onclick="closeQrOverlay()" aria-label="关闭">×</button>
<div class="qr-dialog-title">出库磅单二维码</div>
<div class="pound-slip-capture-inline">
<div class="capture-card">
<div class="capture-row capture-row-plate">
<div class="vehicle-plate">
<span class="vehicle-plate-dot vehicle-plate-dot-lt"></span>
<span class="vehicle-plate-dot vehicle-plate-dot-lb"></span>
<span class="vehicle-plate-text">皖AM8516</span>
<span class="vehicle-plate-dot vehicle-plate-dot-rt"></span>
<span class="vehicle-plate-dot vehicle-plate-dot-rb"></span>
</div>
</div>
<div class="capture-row capture-row-large">
<span class="label">往来单位:</span>
<span class="value">合肥德泽化工科技有限公司</span>
</div>
<div class="capture-row capture-row-large">
<span class="label">物料名称:</span>
<span class="value">30% 工业硫酸(30%)(10.000 吨)</span>
</div>
</div>
<div class="capture-card capture-qrcode">
<div class="capture-id">2026-09-12 出库单ID:a1b2c3d4-e5f6-7890</div>
<div class="capture-qr-wrap">
<img
class="capture-qr-img"
alt="二维码"
src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&amp;data=a1b2c3d4-e5f6-7890"
/>
</div>
<div class="capture-desc">出入库单标识(扫码核验)</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Tab 3: Copy text -->
<section id="panel-copy" class="panel">
<div class="panel-head">复制给司机 · 文案增量</div>
<div class="panel-body">
<div class="grid-2">
<div>
<p class="hint">现状(末行仅司机链接)</p>
<div class="copy-box">【皖AM8516 - 运输计划】
计划号:PO20260912008
车牌:皖AM8516
时间:2026-09-12
【装货 · 安庆市山峡化工有限公司】
时间:2026-09-12
导航:https://www.amap.com/search?query=装货
产品:30% 工业硫酸(30%),10.000吨
【送货 #1 · 合肥德泽化工科技有限公司】
时间:2026-09-12
导航:https://www.amap.com/search?query=送货%20%231
产品:30% 工业硫酸(30%),10.000吨
司机链接:http://121.43.228.102:9911/#/public/pl/transport/Ve-rB5Q3…</div>
</div>
<div>
<p class="hint">目标(追加磅单二维码链接)</p>
<div class="copy-box">【皖AM8516 - 运输计划】
计划号:PO20260912008
车牌:皖AM8516
时间:2026-09-12
【装货 · 安庆市山峡化工有限公司】
时间:2026-09-12
导航:https://www.amap.com/search?query=装货
产品:30% 工业硫酸(30%),10.000吨
【送货 #1 · 合肥德泽化工科技有限公司】
时间:2026-09-12
导航:https://www.amap.com/search?query=送货%20%231
产品:30% 工业硫酸(30%),10.000吨
司机链接:http://121.43.228.102:9911/#/public/pl/transport/Ve-rB5Q3…
<span class="hl">磅单二维码链接:http://121.43.228.102:9911/#/public/pl/pound-qrcode/a1b2c3d4-e5f6-7890</span></div>
</div>
</div>
<p class="hint" style="margin-top:14px">
多张地磅出入库单时:文案可列多行链接,或先弹选择器再复制对应链接(与现有「选择磅单二维码」一致)。待确认。
</p>
</div>
</section>
<script>
document.querySelectorAll('.tab').forEach((btn) => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'))
document.querySelectorAll('.panel').forEach((p) => p.classList.remove('active'))
btn.classList.add('active')
document.getElementById('panel-' + btn.dataset.tab).classList.add('active')
})
})
function openQrOverlay() {
document.getElementById('qrOverlay').classList.add('show')
}
function closeQrOverlay() {
document.getElementById('qrOverlay').classList.remove('show')
}
</script>
</body>
</html>
@@ -0,0 +1,187 @@
# 磅单二维码公开链接与司机页入口 — 设计文档
> **日期**: 2026-09-13
> **状态**: 待实现
> **方案**: 方案 1 — 抽公共展示组件 + 公开只读 API + 独立路由 + 司机页弹层
> **Demo**: `docs/superpowers/demos/2026-09-13-pound-qrcode-public-demo.html`
## 1. 目标
1. 「复制给司机」文案末尾追加**磅单二维码链接**(可多行);链接打开独立公共页,样式与管理端「复制磅单二维码」弹窗一致。
2. 司机填写页(`/#/public/pl/transport/:token`)底栏增加「磅单二维码」;点击后弹层展示同款界面(多单先选再弹)。
## 2. 已确认决策
| # | 项 | 决策 |
|---|-----|------|
| D1 | 多单 · 复制文案 | 多行链接;格式见 D4 |
| D2 | 多单 · 司机页 | 先选一张再弹窗(ActionSheet) |
| D3 | 公开 URL | `#/public/pl/pound-qrcode/{inventoryId}`,免登录按 id 查公开字段 |
| D4 | 文案格式 | 先一行标题 `磅单二维码链接:`,下列 `{业务类型} · {往来单位}:{url}` |
| D5 | 无地磅关联单 | 复制不加磅单段;司机页隐藏第三钮 |
| D6 | 底栏布局 | 同一行三钮:`运输记录 \| 磅单二维码 \| 保存回执` |
| D7 | 实现路径 | 方案 1(共享 View + 公开 API + 扩展司机页 payload) |
| D8 | 作废/已完成单据 | 仍允许只读展示二维码(核验场景) |
## 3. 非目标
- 不做公开 URL token 化 / 访问限流(已知 id 枚举风险,后续可单独立项)
- 不改管理端「复制磅单二维码」图片复制主流程(仍弹窗复制图)
- 不改现场页 `transport-site` 底栏
- 不做小程序端对本能力的移植
- 不扩展公开接口返回现场回执、金额、租户号等非展示字段
## 4. 架构
```text
管理端「复制给司机」
→ arrange / inventoryLinks(已有)
→ buildPlanSummaryText 追加磅单链接块
→ 剪贴板
独立公开页
/#/public/pl/pound-qrcode/:inventoryId
→ GET /api/public/db/inventory/{id}/pound-qr
→ PoundSlipQrcodeView
司机页
/#/public/pl/transport/:token
→ GET /api/public/pl/transport/{token} 含 poundQrcodeItems[]
→ 有 items 显示第三钮
→ 1 条直接弹层 / 多条 ActionSheet
→ 同 pound-qr API → PoundSlipQrcodeView
管理端「复制磅单二维码」
→ DbPoundSlipQrcodeDialog(鉴权 get)
→ 内嵌同一 PoundSlipQrcodeView(样式不漂移)
```
### 4.1 组件边界
| 单元 | 职责 | 依赖 |
|------|------|------|
| `PoundSlipQrcodeView` | 纯展示:黄牌、往来单位、物料、备注、日期+单号、二维码图、说明 | 已解析的公开字段 + 图片 URL;样式类 `.pound-slip-capture-inline` |
| `DbPoundSlipQrcodeDialog` | 管理端弹窗壳、鉴权加载、复制图片 | View + `inventoryApi.get` |
| 公共页 `pound-qrcode/index.vue` | 路由 id → 公开 API → View / 空态 | `publicPlGet` |
| 司机页弹层 | `van-popup` 深色底 + View;多选 ActionSheet | `poundQrcodeItems` + 公开 API |
## 5. 接口
### 5.1 新增 `GET /api/public/db/inventory/{inventoryId}/pound-qr`
- 挂在现有 `public/db/inventory` 路由模块;该前缀已在 `API_ENCRYPT_EXCLUDE_PATHS`。
- 条件:`id` 匹配且 `del_flag=0`;**不**做登录/租户校验。
- 响应(camelCase)仅含:
| 字段 | 说明 |
|------|------|
| `id` | 出入库单 id(二维码内容) |
| `date` | 业务日期展示串 |
| `businessType` | `入库` / `出库` |
| `vehicleNo` | 车牌 |
| `partnerCompany` | 往来单位 |
| `product` | 物料名 |
| `specification` | 规格(可空) |
| `quantity` | 数量 |
| `quantityUnit` | 单位文案,缺省「吨」 |
| `remarks` | 备注(可空;空则不渲染行) |
- 404:不存在或已删。
- Schema 建议:`PublicPoundQrRead`(`app/schemas/db/public_inventory.py`)。
### 5.2 扩展司机页 `PublicTransportPage`
增加:
```ts
poundQrcodeItems?: {
inventoryId: string
businessType: string
partnerCompany: string
label: string // 「出库 · 合肥德泽化工科技有限公司」
}[]
```
来源:运输对应计划下有效 `PlTransportDbLink` → `DbInboundOutbound`(`del_flag=0`)。无则 `[]`。
## 6. 前端交互
### 6.1 路由
`staticRoutes.ts` 增加:
- path: `/public/pl/pound-qrcode/:inventoryId`
- name: `PublicPlPoundQrcode`
- meta: `{ title: '磅单二维码', isHideTab: true, isPublicMobile: true }`
- 加载成功后可将 document/title 改为「入库/出库磅单二维码」。
### 6.2 司机页底栏
- `poundQrcodeItems.length > 0` → 三钮:`运输记录 | 磅单二维码 | 保存回执`。
- 否则保持现有两钮。
- 计划作废/归档导致仅「运输记录」时:若仍有 items,**仍显示「磅单二维码」**(只读核验);「保存回执」按原规则隐藏即可。
- 多条:`showActionSheet({ actions: items.map(i => ({ name: i.label, … })) })`。
### 6.3 复制给司机文案
改 `web-admin/src/utils/pl-driver-summary.ts` 的 `buildPlanSummaryText`:
- 新增可选参数 `poundQrcodeLinks?: { label: string; url: string }[]`。
- 有数据时,在「司机链接」行之后追加:
```text
磅单二维码链接:
出库 · 合肥德泽化工科技有限公司:http://host/#/public/pl/pound-qrcode/{id}
入库 · 安庆市山峡化工有限公司:http://host/#/public/pl/pound-qrcode/{id}
```
- `url` = `{window.location.origin}/#/public/pl/pound-qrcode/{inventoryId}`。
- 调用方(`usePlPlanArrangeActions` / `pl-plan-view-tabs` 等)从已有 `dibangInventoryRecords` 组装 links;无记录则不传。
### 6.4 二维码图片
与现 `DbPoundSlipQrcodeDialog` 一致:对 `id` 编码生成二维码图(当前为 qrserver);产品标签拼接规则(规格后缀去重)复用现函数。
## 7. 错误处理
| 场景 | 行为 |
|------|------|
| 公开页 id 无效/已删 | 404 → 「链接无效或单据不存在」 |
| 公开页网络失败 | 「加载失败,请稍后重试」 |
| 司机弹层加载失败 | Toast「加载二维码失败」 |
| items 为空 | 不渲染第三钮 |
| 无 dibang 关联复制 | 文案无磅单段 |
| 二维码图 CDN 失败 | 公共页保留错误提示;管理端保持现有提示行为 |
## 8. 测试要点
1. 单张地磅单:复制文案含标题 + 一行 label:url;打开样式与管理端弹窗一致。
2. 多张:文案多行;司机页 ActionSheet → 弹层对应正确单。
3. 未勾选生成地磅单:无磅单段、无第三钮。
4. 管理端「复制磅单二维码」复制图片仍可用,样式不回退。
5. 计划作废后:有 items 时可只读打开二维码;保存回执按原规则。
6. 删单后旧链接:公开页 404 文案。
## 9. 主要改动文件(预估)
**Backend**
- `app/api/db/public_inventory.py` — 新增 `pound-qr`
- `app/schemas/db/public_inventory.py` — `PublicPoundQrRead`
- `app/api/pl/public_transport.py` — `_build_public_page` 填 `poundQrcodeItems`
- `app/schemas/pl/...` — `PublicTransportPageRead` 字段
**web-admin**
- `components/business/db/PoundSlipQrcodeView.vue`(新建)
- `components/business/db/DbPoundSlipQrcodeDialog.vue`(改用 View)
- `views/public/pl/pound-qrcode/index.vue`(新建)
- `router/routes/staticRoutes.ts`
- `views/public/pl/transport/index.vue`
- `api/pl/public-transport.ts`、`api/db/public-inventory.ts`(或并列 pound-qr API)
- `utils/pl-driver-summary.ts`
- `composables/usePlPlanArrangeActions.ts`(及复制入口调用链)
## 10. 验收标准
司机通过复制文案中的磅单链接,或在司机页点击「磅单二维码」,看到的界面与后台「复制磅单二维码」弹窗同款;多单可选;无关联单时链接与按钮均不可见。