2026-07-01-attachment-drag-upload-design.md 12 KB

附件上传支持拖拽 — 功能设计文档

日期:2026-07-01
状态:待实现
需求来源:客户希望在保留「点击选择文件」的前提下,增加「拖拽文件到上传区」能力
已确认范围:方案 1 — 仅附件类上传(类型 A + B + C);Logo/商品媒体/头像等(类型 D、E)留二期


1. 背景与目标

1.1 背景

web-admin 中约 30 个页面/组件使用 fetchUploadAttachment 实现附件上传,UI 以 OpenSpec「附件上传卡片化布局」为准(权威参考:ProtocolFormDialog.vue「协议附件」)。当前均仅支持点击 140×96 上传卡片触发文件选择,不支持拖拽。

1.2 目标

  1. 在不改变现有点击上传交互的前提下,为方案 1 范围内所有上传区增加拖拽落文件能力。
  2. 拖拽与点击共用同一套上传逻辑(校验 → 占位 → fetchUploadAttachment → 回填/失败移除)。
  3. 按业务域分批独立改造,每批改完可单独回归,降低全站一次性改动风险。

1.3 非目标(一期不做)

  • 单图/Logo 按钮上传(快递 Logo、站点图片、机构 Logo 等)
  • 商品主图/轮播/详情媒体(system/product)
  • 订单取消佐证 picture-card、用户头像
  • 后端 API 变更
  • 抽成全站统一 AttachmentUploadField 组件(本期各页独立加 drop 能力,避免大范围重构)
  • 文件夹拖拽、剪贴板粘贴上传

2. 改造范围清单

2.1 类型 A — 页面内联卡片化附件(22 文件,约 33 个上传区)

ID 文件 上传区数 说明
A1 web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue 2 协议附件、缴费凭证(upload-card--sm)
A2 web-admin/src/views/cell-storage/protocol-audit/PaymentInfoTab.vue 2 上传凭证、上传附件
A3 web-admin/src/views/cell-storage/protocol-audit/StorageCertTab.vue 1 储存证附件
A4 web-admin/src/views/cell-storage/protocol-audit/FullmoonTodoTab.vue 1 附件
A5 web-admin/src/views/research-donation/list/DonationFormDialog.vue 2 两处附件
A6 web-admin/src/views/quality-inspection/env-detect-record/index.vue 1 检测附件
A7 web-admin/src/views/quality-inspection/storage-release-review/StorageReleaseReviewDialog.vue 1 审核附件
A8 web-admin/src/views/production/sop/index.vue 1 SOP 附件
A9 web-admin/src/views/production/cell-product/index.vue 1 产品图片(卡片布局)
A10 web-admin/src/views/pilot-service/channel/index.vue 1 渠道协议附件
A11 web-admin/src/views/pilot-service/channel/ChannelServiceOrderTab.vue 2 充值/退款附件
A12 web-admin/src/views/pilot-service/channel-service-order/ServiceOrderTxnDialog.vue 1 流水附件
A13 web-admin/src/views/pilot-service/channel-service-order-finance/index.vue 1 财务确认附件
A14 web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue 2 附件 ×2
A15 web-admin/src/views/pilot-service/clinical-payment-confirm/RefundConfirmTab.vue 2 附件 ×2
A16 web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue 4 知情同意/诊断等
A17 web-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue 5 诊断/交付等(含非标准 attachment-trigger-card 样式,需对齐 drop 区)
A18 web-admin/src/views/pilot-service/pilot-application/ClinicalPaymentTab.vue 2 附件、退费附件
A19 web-admin/src/views/pilot-service/outbound-release-review/OutboundReleaseReviewDialog.vue 1 审核附件
A20 web-admin/src/views/pilot-service/infusion-schedule-management/InfusionScheduleHandoverDialog.vue 1 交接附件(与 C 类物料区并存)
A21 web-admin/src/views/pilot-service/checkup-management/CheckupDetailDialog.vue 1 上传报告
A22 web-admin/src/views/pilot-service/checkup-management/CheckupMarkCheckedDialog.vue 1 上传报告

2.2 类型 B — ProtocolFinanceConfirmFields 共享组件(1 组件 + 6 消费页,约 8 个上传区)

ID 文件 上传区数 说明
B0 web-admin/src/components/business/protocol-finance-confirm-fields/index.vue 1 改组件即覆盖下列引用
B1 web-admin/src/views/quality-inspection/sample-reception/index.vue 2 单条接收 + 批量接收
B2 web-admin/src/views/cell-storage/payment-confirm/index.vue 1 确认收款附件
B3 web-admin/src/views/cell-storage/payment-confirm/RefundConfirmTab.vue 1 退费财务附件
B4 web-admin/src/views/cell-storage/payment-confirm/InvoiceRequestTab.vue 1 开票附件
B5 web-admin/src/views/production/production-work-order/TaskPaperAttachmentsPanel.vue 1 线下纸质文档
B6 web-admin/src/views/quality-inspection/qc-material/QcMaterialBatchAttachmentDialog.vue 1 质检附件

2.3 类型 C — 表格行内纸质材料(2 文件)

ID 文件 说明
C1 web-admin/src/components/business/outbound-material-form-sections/index.vue 出库/交接物料弹窗复用;每行 paper-att-card--trigger
C2 web-admin/src/views/pilot-service/outbound-release-review/OutboundReleaseMaterialDialog.vue 独立实现同类行内上传,需与 C1 同步改法

引用 C1 的页面(改 C1 后需回归):OutboundReleaseMaterialDialog.vue、InfusionScheduleHandoverDialog.vue(物料区)。

2.4 二期范围(本期不改)

类型 D(8 文件):express、site、clean-room、cell-sample-type、insurance-company、checkup-institution、ThirdPartyCompanyTab、category。

类型 E(3 文件):system/product、system/order、UserProfileDialog。


3. 交互与视觉规范

3.1 Drop Zone 范围

类型 Drop Zone 元素 说明
A / B .attachment-upload 根容器 包含 .attachment-list(上传卡片 + 已传附件卡片)及下方 .upload-hint 所在整块区域
C 该行 .paper-att-list 或附件 cell 容器 行内空间较小,drop 高亮仅作用于当前行的附件列表 flex 区

不采用 Element Plus el-upload 的 drag 大区域模式(会替换 slot、破坏 140×96 卡片布局)。

3.2 拖拽态视觉

在 .attachment-upload 上增加修饰类 .is-dragover(类型 C 对应 .paper-att-list.is-dragover):

  • border: 1px dashed #10b981
  • background: #ecfdf5
  • border-radius: 8px
  • 可选:padding: 8px(仅 dragover 时,避免布局跳动用 outline 或 box-shadow 代替 padding 亦可)

.upload-hint 默认文案追加:「支持拖拽或点击上传」(保留原有格式/大小限制说明)。

3.3 行为规则

  1. dragenter / dragover:preventDefault(),设置 is-dragover = true(需在 dragleave 时判断 relatedTarget 仍在容器内再清除,避免闪烁)。
  2. drop:preventDefault(),清除 is-dragover,读取 event.dataTransfer.files。
  3. 多文件:按文件顺序逐个调用与 @change 相同的上传处理(与 :multiple="true" 一致)。
  4. 校验:单文件 ≤ 20MB、accept 类型与现有点击上传一致;失败项 ElMessage.error 并跳过,不影响其他文件。
  5. 重复文件:与点击上传相同,依赖现有 clearFiles / 数组 push 逻辑。
  6. 上传中:dragover 期间若存在 _uploading,仍允许 drop(排队上传);提交拦截逻辑不变。
  7. 禁用/只读:只读态(如 TaskPaperAttachmentsPanel 的 disabled)不绑 drop 事件。

3.4 与 OpenSpec 关系

实现后 SHALL 在 openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md「附件上传卡片化布局」章节补充:

  • Drop zone 为 .attachment-upload
  • .is-dragover 视觉
  • .upload-hint 文案含「拖拽或点击」
  • 表格行内纸质材料 drop 区约定

4. 技术方案

4.1 推荐:轻量 composable + 各页独立挂载

新增 web-admin/src/composables/useAttachmentDropZone.ts(仅工具,非 UI 组件):

// 伪代码示意
export function useAttachmentDropZone(options: {
  onFiles: (files: File[]) => void
  disabled?: Ref<boolean>
}) {
  const isDragover = ref(false)
  // dragenter, dragleave, dragover, drop handlers
  return { isDragover, dropZoneHandlers }
}

各页在 .attachment-upload 上:

<div
  class="attachment-upload"
  :class="{ 'is-dragover': isDragover }"
  v-bind="dropZoneHandlers"
>

onFiles 内对每个 File 调用现有 handleAttachmentChange({ raw: file }) 或等价函数。

理由:满足「每处独立修改」——各文件显式挂载 composable,diff 清晰;逻辑 DRY 但不引入新 Vue 组件,回归面可控。

4.2 类型 B 实施策略

改 ProtocolFinanceConfirmFields 一处,对 B1–B6 做全量回归(视为独立一批 PR,不与 A 类混在同一 PR)。

4.3 类型 C 实施策略

在 handlePaperAttachmentChange 同级增加 drop 处理;drop zone 为行级 .paper-att-list。C1、C2 同批修改,并回归出库物料弹窗与输注交接弹窗。

4.4 全局样式(可选)

若多页重复 .is-dragover 样式,可在 web-admin/src/assets/styles/core/app.scss 增加:

.attachment-upload.is-dragover { ... }
.paper-att-list.is-dragover { ... }

各页 scoped 样式可省略重复定义。


5. 分批实施顺序

批次 范围 文件数 验收重点
0 composable + 全局 dragover 样式 + OpenSpec 补充 2–3 单元/手工验证 composable
1 试点 A1 ProtocolFormDialog 1 定稿交互,2 个上传区
2 细胞存储 A2–A5 + B0 + B2–B4 6 + 组件 协议审核、付款确认、捐赠
3 质检 A6–A7 + B1 + B6 4 + 组件已含 样本接收、环境检测、放行
4 中试上 A10–A15 6 渠道、临床付款确认
5 中试下 A16–A22 + C1–C2 9 + 2 临床申请、出库、体检、纸质材料
6 生产 A8–A9 + B5 3 SOP、细胞产品、工单纸质文档

每批:仅改该批文件 → 手工测试 → 合并,再进入下一批。


6. 测试要点(每批通用)

场景 预期
点击上传卡片选 1 个文件 与改前一致
拖拽 1 个合法文件到 upload 区 出现占位卡片 → 上传成功 → 可预览/下载/删除
拖拽多个文件 全部入队上传
拖拽超大文件(>20MB) 提示错误,不上传
拖拽不支持类型 浏览器/业务校验拒绝或跳过
dragover 视觉 进入区域高亮,离开恢复
上传中提交表单 仍提示「附件上传中,请稍候」
只读/禁用 无 drop 响应

7. 风险与缓解

风险 缓解
多文件 drop 与 @change 并发竞态 复用 ProtocolFinanceConfirmFields 已有 _pendingList 模式;A 类页面若存在竞态可参考
ApplicationDetailDialog 样式不统一 该页单独测试 5 个上传区
C1/C2 行内区域过小 drop 高亮整行附件 flex 区,不要求拖到小卡片上
浏览器 dragleave 闪烁 composable 内 counter 或 relatedTarget 检查

8. 相关文档

  • OpenSpec:openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md §附件上传卡片化布局
  • UI 规范:docs/web-admin-ui-spec-fresh-mint.md
  • 权威参考页:web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue
  • 实施计划:docs/superpowers/plans/2026-07-01-attachment-drag-upload.md