2026-07-01-qc-work-order-attachment-export-design.md 6.8 KB

质检工单批量导出任务附件 — 设计文档

日期:2026-07-01
状态:待实现
范围:web-admin 【质检工单】【我的质检任务】
已确认:方案 A — 纯前端 JSZip 打包下载
已确认:导出过程需可见进度反馈(任务较慢)


1. 背景与目标

1.1 业务需求

用户在【质检工单】或【我的质检任务】中勾选一条或多条质检工单,将该工单下所有检测批次、所有质检任务的附件打包为 ZIP 下载。

目录结构:

{工单}/
  {批次}/
    {任务}/
      附件文件…

1.2 目标

  1. 工单卡片支持多选(checkbox)。
  2. 工具栏提供「导出附件」按钮(btn-ghost + Download 图标)。
  3. 浏览器端聚合附件、按层级建目录、生成 ZIP 并触发下载。
  4. 导出过程展示进度弹窗(阶段 + 百分比 + 当前文件名),避免用户误以为卡死。

1.3 非目标(一期不做)

  • 后端流式 ZIP / 异步任务中心
  • 跨分页保留勾选
  • 工单级其他附件(诊断附件、样本接收附件等)
  • 【我的质检任务】仅导出「本人负责任务」的子集模式(与 manage 一致,导出工单下全部任务附件)

2. 技术方案

2.1 方案选定:纯前端 JSZip

步骤 说明
1 收集已选 workOrderId[]
2 POST /web/admin/qc-work-orders/inspection-batches/batch 获取批次及内嵌 tasks[].attachmentFiles
3 扁平化附件清单,计算 ZIP 内路径
4 逐个 fetchAttachmentBlob(预签名 URL)写入 JSZip
5 zip.generateAsync({ type: 'blob' }, onUpdate) 生成压缩包
6 FileSaver.saveAs 下载

依赖:jszip、file-saver(已存在于 package.json)。

2.2 附件数据来源

  • 表字段:biz_qc_task.third_party_attachments(逗号分隔 URL)
  • API 字段:attachmentFiles(含 fileUrl、fileName)
  • 实验室自检与第三方检测共用该字段

2.3 目录命名

层级 规则
工单 {workOrderNo};若重名追加 _样本{sampleCode} 等短标识
批次 {batchNo}_{batchName};无 batchNo 时用 批次{orderNum}
任务 {taskNo}_{itemName}
文件 优先 fileName;同目录重名用 (2) 后缀

无附件的任务/批次不创建空目录。所选工单均无附件时提示「所选工单暂无附件」。

2.4 安全与权限

  • 批次批量接口已受 QC_TASK_READONLY_MENU_CODES 与租户隔离约束。
  • 两菜单共用实现;导出范围为工单下全部任务附件(非仅当前用户任务)。

3. 进度弹窗 UX(核心补充)

环境检测页的「批量下载附件」仅按钮 loading,无细粒度进度。本功能附件量大、耗时长,必须使用独立进度弹窗。

3.1 组件

新建:web-admin/src/components/business/attachment-zip-progress-dialog/index.vue

  • el-dialog,append-to-body,modal-class="protocol-dialog-mask"(无背景模糊)
  • close-on-click-modal="false",show-close="false"(进行中不可点遮罩关闭)
  • 宽度约 480px,单栏居中

3.2 三阶段进度

整体进度条 0–100%,按阶段加权:

阶段 权重 说明
preparing 0–5% 请求批次/任务元数据
downloading 5–85% 逐个拉取附件 blob 并 zip.file()
zipping 85–100% generateAsync 压缩(使用 JSZip metadata.percent)

展示文案示例:

  • 准备中:正在加载工单附件清单…
  • 下载中:正在下载附件(12/48)— 报告.pdf
  • 压缩中:正在生成压缩包…

副文案:当前工单 ZJGD… / 批次 首批检测(可选,简短一行 text-muted)。

3.3 进度条

  • el-progress:stroke-width="8",:percentage="overallPercent",:status 完成时 success
  • 数字百分比与「已完成 N / 共 M 个文件」并列

3.4 完成与失败

结果 行为
全部成功 进度 100%,状态 success,1s 后自动关闭弹窗;ElMessage.success('已打包下载 N 个附件')
部分失败 仍生成 ZIP(跳过失败项);关闭后 ElMessage.warning('已下载 N 个,M 个失败')
全部失败 不下载;ElMessage.error('附件下载失败,请稍后重试')
无附件 不打开进度弹窗;ElMessage.warning('所选工单暂无附件')

3.5 取消

  • 底部提供「取消」按钮(Close 图标 + 文案)
  • 使用 AbortController 中断进行中的 fetch;设置 cancelled 标志跳过后续文件
  • 取消后关闭弹窗,ElMessage.info('已取消导出')
  • 已写入 ZIP 的 blob 丢弃,不触发 saveAs

3.6 并发与性能

  • 附件下载串行(与环境检测一致),避免浏览器连接数耗尽;进度更准确
  • 可选:限制单次最多 50 个工单 或 500 个文件,超出时 ElMessageBox.confirm 二次确认

4. 页面改动

4.1 勾选

在 QcWorkOrderSingleCard 卡片头部左侧增加 el-checkbox:

  • QcWorkOrderProtocolGroup / QcWorkOrderSampleGroup 内每张工单卡片各自可勾选
  • 状态由 QcWorkOrderTabPanel 持有:selectedWorkOrderIds: Set<string>
  • 翻页或重新搜索时清空选择

4.2 工具栏

QcWorkOrderTabPanel.vue 在卡片列表上方增加(manage / mine 均显示):

已选 N 项 | [导出附件](btn-ghost,N=0 时 disabled)

4.3 逻辑抽离

文件 职责
@/utils/attachment-zip.ts 从 env-detect-record 抽出 fetchAttachmentBlob、ensureUniqueZipEntryName、sanitizeZipPathSegment
@/utils/qc-work-order-attachment-export.ts 构建导出清单、ZIP 路径、runQcWorkOrderAttachmentExport()
attachment-zip-progress-dialog 进度 UI
useQcWorkOrderAttachmentExport.ts 组合 API + 进度回调 + 弹窗显隐

5. 接口

一期不新增后端接口,复用:

  • POST /web/admin/qc-work-orders/inspection-batches/batch
  • POST /web/admin/attachment/download-url

6. 测试要点

# 场景 预期
1 勾选 1 个工单,含多批次多任务附件 ZIP 目录三层正确,文件可打开
2 勾选多个工单 根目录下多个工单文件夹
3 工单无附件 提示暂无附件,不弹进度窗
4 导出中取消 中断下载,无文件落盘
5 单个文件 404 其余正常打包,warning 提示失败数
6 【我的质检任务】与【质检工单】 行为一致
7 协议组 / 样本组 各工单独立勾选、导出

7. 后续演进

若生产反馈单次导出经常超过 500 文件或超时,再评估方案 B(后端 StreamingResponse ZIP)并保留相同目录规范与进度语义(改为轮询任务状态)。