Files
shanxia2.0/docs/superpowers/specs/2026-09-23-pl-plan-archive-attachment-zip-export-design.md
T
wuzilongandCursor f5b76ef711 docs: add plan-archive data-and-attachments ZIP export design.
Capture brainstorming decisions for coexisting Excel and ZIP export, reference-zip folder layout, document attachments plus hazard certs, and relative-path hyperlinks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 12:36:18 +08:00

9.2 KiB
Raw Blame History

运输计划归档 · 数据及附件 ZIP 导出 — 设计文档

日期: 2026-09-23
状态: 待审阅
页面: web-admin /#/pl/plan-module/plan-archive
前置: docs/superpowers/specs/2026-06-25-pl-plan-archive-design.md、现有归档 Excel 导出(exportFormat=archive)、load_plan_document_attachments_by_plan_ids
参考包形态: 用户提供的 加油汇总表(yyyyMMddHHmm).zip(根目录 + (总表)*.xlsx + 扁平 附件/{序号}_*.ext)
实现策略: 后端一站式打 ZIP;Excel 用相对路径超链接(不做 WPS DISPIMG 内嵌)

目标

在运输计划归档页「导出」下拉中,保留现有 Excel 导出,并新增「数据及附件 ZIP」能力:一次下载包含归档 Excel + 计划相关附件文件,目录结构对齐参考 ZIP;用户须先解压,再在解压目录中打开 Excel,通过超链接打开附件。


已确认决策(2026-09-23)

# 决策 说明
D1 并存 保留「导出筛选结果 / 导出已选」Excel;新增两项 ZIP
D2 ZIP = 数据 + 附件 包内同时有归档 xlsx 与附件文件
D3 范围对称 ZIP 亦拆「筛选结果」「已选」两项
D4 结构对齐参考包 计划归档(yyyyMMddHHmm)/(总表)计划归档.xlsx + 附件/ 扁平文件;不做 WPS DISPIMG / cellimages 内嵌
D5 路径引用 Excel 附件列用相对路径 HYPERLINK;须先解压再打开
D6 单据附件范围 与列表「单据附件」一致(6 类,见 §3.1)
D7 危化品证一并导出 计划产品购买证 / 运输证 / 合法使用证明 URL
D8 后端一站式 扩展现有 plan export;浏览器不拼包

非目标(本期不做)

  • WPS DISPIMG / xl/cellimages.xml 内嵌配图(体积翻倍、实现复杂)
  • 将图片真正嵌入标准 OOXML drawing(可选二期)
  • 异步导出任务中心 / 下载通知
  • 车辆/人员证件、承运商附件、合同扫描件、地磅原始磅单文件(除非已落入 §3 附件来源)
  • 新增独立权限码(沿用现归档导出能力;需要时再加 pl:plan-archive:export-zip)

一、前端交互

1.1 入口

页面:web-admin/src/views/pl/plan-archive/index.vue
组件:ListExportDropdown(需扩展,或归档页专用包装)

菜单项 产物
导出筛选结果 归档 .xlsx(现状)
导出已选 归档 .xlsx(现状)
导出筛选结果(数据及附件 ZIP) .zip
导出已选(数据及附件 ZIP) .zip
  • 无勾选时「导出已选*」仍禁用(与现状一致)。
  • ZIP 请求带上与 Excel 相同的筛选参数 / ids,并设置 exportFormat: 'archive_zip'。
  • 下载:复用 pickBlobFromExportResponse + triggerBlobDownload;默认文件名 计划归档(yyyyMMddHHmm).zip。

1.2 加载与提示

  • 按钮 loading 直至响应结束。
  • 成功:「导出成功」。
  • 业务错误(超行数等):展示后端文案。
  • 可选:若响应附带「部分附件失败」说明,用 warning 提示(若实现为包内 导出说明.txt,前端可不解析)。

二、后端接口与流程

2.1 接口

  • 方法/路径: 现有 POST /api/.../transport/plan/export(与现导出同路由)
  • 新增取值: 现有字段 exportFormat 增加取值 archive_zip(与现有 archive 并列)
  • 筛选 / ids / columns / 权限: 与 exportFormat=archive 相同;价格列仍受 user_can_view_archive_prices 过滤
  • 响应: Content-Type: application/zip;Content-Disposition 含时间戳文件名

2.2 打包流程

  1. 按现逻辑查询计划(含 EXPORT_MAX_ROWS 校验)。
  2. build_plan_archive_export_rows(...) 生成归档行。
  3. 收集附件(§3):单据附件 + 危化三证;按计划去重 URL。
  4. 为每个唯一计划确定附件序号 = 该计划在归档表中首行的 rowNo。
  5. 从对象存储下载文件,写入内存/临时 ZIP:
    • 计划归档(yyyyMMddHHmm)/(总表)计划归档.xlsx
    • 计划归档(yyyyMMddHHmm)/附件/{序号}_{类型标签}_{短哈希}.{ext}
  6. 生成 xlsx:原归档列 + 附件_1…附件_K;仅计划首行写 HYPERLINK。
  7. 返回 ZIP 流。

2.3 建议实现落点

层 建议
API backend/app/api/pl/plan.py → export_plans 分支 archive_zip
服务 新建如 pl_plan_archive_zip_export.py:收集附件、命名、写 zip、改 xlsx 附件列
复用 build_plan_archive_export_rows、load_plan_document_attachments_by_plan_ids、现有 storage 读接口、export_rows_to_xlsx(或等价)再注入超链接列

三、附件范围与命名

3.1 单据附件(与列表「单据附件」一致)

# 来源 标签示例 字段
1 运输单整单司机回传 司机 pl_transport.feedback_images
2 司机按段回传 装货·司机 / 送货#N·司机 pl_transport_driver_stop_feedback.attachment_urls
3 现场按段回传 装货·现场 / 送货#N·现场 pl_transport_site_stop_feedback.attachment_urls
4 现场链接回传 现场 / 现场·{产品} pl_transport_site_link.site_feedback_images
5 入库单现场 入库现场 pl_inbound_order.site_feedback_images
6 出库单现场 出库现场 pl_outbound_order.site_feedback_images

同一计划内相同 URL 去重。文件类型以实际上传为准(图片、PDF 等)。

3.2 危化品证(计划产品)

类型 字段
购买证 pl_plan_product.purchase_cert_url
运输证 pl_plan_product.transport_cert_url
合法使用证明 pl_plan_product.legal_use_cert_url

均为逗号分隔多 URL;空则跳过。多产品时标签可带产品名缩写以防文件名冲突。

3.3 文件命名

{序号两位}_{类型标签}_{短哈希}.{ext}

示例:01_装货·司机_a1b2c3.jpg、01_购买证_d4e5f6.pdf
路径非法字符按现有 zip 命名惯例替换为 _。

3.4 序号规则

  • 归档表 序号(rowNo)仍按「段 × 产品」逐行递增(与现 Excel 导出一致)。
  • 附件文件前缀 = 该计划首行的 rowNo(两位补零)。
  • 同一计划多附件共用同一前缀;Excel 仅在该计划首行填写 附件_* 列。

四、Excel 附件列

规则 说明
列名 附件_1 … 附件_K
K 本批各计划附件数的最大值;上限 20(超出仍写入 附件/ 目录,表内只链前 20 个)
单元格 相对路径超链接,如 HYPERLINK("附件/01_装货·司机_a1b2.jpg","装货·司机")
填行 仅计划首行;同计划其余行留空
打开方式 用户须解压 ZIP 后,在解压目录中打开 xlsx

纯 exportFormat=archive 的 Excel 不增加附件列(行为不变)。


五、ZIP 目录形态(对齐参考包)

计划归档(202609231230)/
  (总表)计划归档.xlsx
  附件/
    01_装货·司机_a1b2.jpg
    01_购买证_c3d4.pdf
    02_送货#1·现场_e5f6.jpg
  导出说明.txt          ← 仅当存在下载失败等时可选写入

与参考包差异(有意为之):

  • Excel 使用标准 HYPERLINK,不使用 WPS DISPIMG / 内嵌 xl/media(避免体积翻倍与专有格式)。
  • 文件名带类型标签,便于人工辨认(参考包为 序号_hash)。

六、边界、上限与错误

场景 行为
超过 EXPORT_MAX_ROWS 400,提示缩小范围
建议软上限 单次附件文件数约 2000 或总下载约 500MB(实现时定一默认);超限提示缩小范围
无计划 提示无可导出数据
有计划、零附件 仍返回 ZIP(仅 xlsx;可无 附件/)
单个文件下载失败 跳过该文件;可选写入 导出说明.txt;其余继续
非图片 正常入包 + 超链接
权限 与现归档导出相同(租户 + apply_pl_scope)

七、测试要点

  1. 下拉四项文案与产物类型正确;筛选 / 已选范围正确。
  2. 解压后目录对齐 §五;从解压目录打开 xlsx,点击附件列能打开对应文件。
  3. 单据 6 类 + 危化三证均出现;与列表单据附件及产品证书一致。
  4. 多段多产品计划:附件只挂首行,前缀=该行序号。
  5. 无附件 / 部分失败 / 超行数行为符合 §六。
  6. 纯 Excel 导出回归:无附件列、无 zip。

建议单测: 附件收集与命名、首行挂载序号、zip 内相对路径字符串;可不测真实 MinIO(mock 字节流)。


八、架构小结

[ListExportDropdown ZIP command]
        │  exportFormat=archive_zip + filters/ids
        ▼
[POST plan/export]
        │
        ├─ build_plan_archive_export_rows
        ├─ load_plan_document_attachments_by_plan_ids
        ├─ load plan product cert URLs
        ├─ fetch storage objects
        ├─ write xlsx (+ HYPERLINK cols) + 附件/*
        └─ stream application/zip

单元边界清晰:导出查询与行构建复用现逻辑;ZIP 组装为独立服务函数,便于单测与后续改命名规则。