日期:2026-07-01
状态:待实现
需求来源:客户希望在保留「点击选择文件」的前提下,增加「拖拽文件到上传区」能力
已确认范围:方案 1 — 仅附件类上传(类型 A + B + C);Logo/商品媒体/头像等(类型 D、E)留二期
web-admin 中约 30 个页面/组件使用 fetchUploadAttachment 实现附件上传,UI 以 OpenSpec「附件上传卡片化布局」为准(权威参考:ProtocolFormDialog.vue「协议附件」)。当前均仅支持点击 140×96 上传卡片触发文件选择,不支持拖拽。
fetchUploadAttachment → 回填/失败移除)。system/product)picture-card、用户头像AttachmentUploadField 组件(本期各页独立加 drop 能力,避免大范围重构)| 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 | 上传报告 |
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 | 质检附件 |
| 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(物料区)。
类型 D(8 文件):express、site、clean-room、cell-sample-type、insurance-company、checkup-institution、ThirdPartyCompanyTab、category。
类型 E(3 文件):system/product、system/order、UserProfileDialog。
| 类型 | 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 卡片布局)。
在 .attachment-upload 上增加修饰类 .is-dragover(类型 C 对应 .paper-att-list.is-dragover):
border: 1px dashed #10b981background: #ecfdf5border-radius: 8pxpadding: 8px(仅 dragover 时,避免布局跳动用 outline 或 box-shadow 代替 padding 亦可).upload-hint 默认文案追加:「支持拖拽或点击上传」(保留原有格式/大小限制说明)。
preventDefault(),设置 is-dragover = true(需在 dragleave 时判断 relatedTarget 仍在容器内再清除,避免闪烁)。preventDefault(),清除 is-dragover,读取 event.dataTransfer.files。@change 相同的上传处理(与 :multiple="true" 一致)。accept 类型与现有点击上传一致;失败项 ElMessage.error 并跳过,不影响其他文件。clearFiles / 数组 push 逻辑。_uploading,仍允许 drop(排队上传);提交拦截逻辑不变。TaskPaperAttachmentsPanel 的 disabled)不绑 drop 事件。实现后 SHALL 在 openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md「附件上传卡片化布局」章节补充:
.attachment-upload.is-dragover 视觉.upload-hint 文案含「拖拽或点击」新增 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 组件,回归面可控。
改 ProtocolFinanceConfirmFields 一处,对 B1–B6 做全量回归(视为独立一批 PR,不与 A 类混在同一 PR)。
在 handlePaperAttachmentChange 同级增加 drop 处理;drop zone 为行级 .paper-att-list。C1、C2 同批修改,并回归出库物料弹窗与输注交接弹窗。
若多页重复 .is-dragover 样式,可在 web-admin/src/assets/styles/core/app.scss 增加:
.attachment-upload.is-dragover { ... }
.paper-att-list.is-dragover { ... }
各页 scoped 样式可省略重复定义。
| 批次 | 范围 | 文件数 | 验收重点 |
|---|---|---|---|
| 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、细胞产品、工单纸质文档 |
每批:仅改该批文件 → 手工测试 → 合并,再进入下一批。
| 场景 | 预期 |
|---|---|
| 点击上传卡片选 1 个文件 | 与改前一致 |
| 拖拽 1 个合法文件到 upload 区 | 出现占位卡片 → 上传成功 → 可预览/下载/删除 |
| 拖拽多个文件 | 全部入队上传 |
| 拖拽超大文件(>20MB) | 提示错误,不上传 |
| 拖拽不支持类型 | 浏览器/业务校验拒绝或跳过 |
| dragover 视觉 | 进入区域高亮,离开恢复 |
| 上传中提交表单 | 仍提示「附件上传中,请稍候」 |
| 只读/禁用 | 无 drop 响应 |
| 风险 | 缓解 |
|---|---|
多文件 drop 与 @change 并发竞态 |
复用 ProtocolFinanceConfirmFields 已有 _pendingList 模式;A 类页面若存在竞态可参考 |
ApplicationDetailDialog 样式不统一 |
该页单独测试 5 个上传区 |
| C1/C2 行内区域过小 | drop 高亮整行附件 flex 区,不要求拖到小卡片上 |
| 浏览器 dragleave 闪烁 | composable 内 counter 或 relatedTarget 检查 |
openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md §附件上传卡片化布局docs/web-admin-ui-spec-fresh-mint.mdweb-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vuedocs/superpowers/plans/2026-07-01-attachment-drag-upload.md