Files
art-design/docs/superpowers/specs/2026-08-06-stock-item-attachment-upload-design.md
T
2026-08-06 14:34:28 +08:00

121 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 备货单车辆明细附件上传 设计
- 日期:2026-08-06
- 分支:feature/crm_pc
- 涉及前端:`src/views/crmpc/stocking/Add.vue`、`src/composables/useStockingForm.ts`
- 涉及后端:`stock_order_items.attachment`(已加字段)、备货单 store/update/show 接口
## 1. 背景
添加备货单(`Add.vue`)时,车辆明细(`items`,每行一辆车)目前只能录入车型 / 颜色 / 价格等字段,**没有附件**。业务需要每辆车能上传若干附件(如该车相关文件),随车辆一起保存。
后端 `stock_order_items` 表已新增 `attachment` 字段(本次由后端同事完成)。
## 2. 需求
- 添加备货单时,车辆明细**每辆车**可上传**多个**附件。
- UI 为**行内紧凑**形态:每行一个「上传附件」入口,已传文件以文件名标签列出、可删除,不撑高行高。
- 附件随车辆数据一起提交,存入 `stock_order_items.attachment`。
- 字段名统一用 `attachment`,值为 `file_name` 字符串数组(与备货单级 `contract`/`pi_doc`/`transaction_slip` 的存储模式一致)。
## 3. 现状(复用基础)
- 车辆明细 `items` 由 `useStockingForm(false)` 提供,每行 `item` 是 `ItemRow`。
- 备货单级文件已有成熟模式:前端维护 `FileItem[]`(`{name, url, file_name}`,带 url 供预览/下载),提交时映射成 `file_name[]` 存入 `baseForm.contract` 等。
- 上传走通用接口 `${VITE_API_URL}/ticket/upload?scene=stock_orders`,header 带 token(`useUploadHeaders`),成功返回 `{ data: { original_name, file_url, file_name } }`。**此接口无需改动。**
- 现成组件 `MultiFileUpload`(120px 大拖拽区)适合整页文件区,不适合塞进行内——故行内用 `el-upload` 直接内联。
## 4. 设计
### 4.1 数据层 `useStockingForm.ts`
- `ItemRow` 类型增加 `attachment_files: { name: string; url: string; file_name: string }[]`。
- `newItem()` 初始化 `attachment_files: []`。
- 提交映射 `mappedItems` 增加:
```ts
attachment: (it.attachment_files || []).map((f) => f.file_name)
```
- 详情回填 `populateFromDetail`:从后端返回的 `it.attachment` 反解为 `attachment_files`(格式见 4.4,Add 为新建不走回填,但 composable 与 edit 共享,一并支持)。
- `copyRow` 已是 `JSON.parse(JSON.stringify(...))`,自动带上附件,无需改。
- 草稿 `pickDraftPayload` 已含 `items: toRaw(items.value)`,附件自然入草稿,无需改。
- 新增车辆附件上传/删除方法(仿现有 `handleContractUpload` 等):
- `handleItemAttachmentUpload(response: any, idx: number)`:校验 `response.code===200`,把 `{ name: data.original_name, url: data.file_url, file_name: data.file_name }` push 进 `items.value[idx].attachment_files`。
- `removeItemAttachment(idx: number, fileIdx: number)`:从 `items.value[idx].attachment_files` 删除指定项。
### 4.2 UI 层 `Add.vue`
在车辆明细 `v-for="(item, idx) in items"` 的行内末尾增加一个 `ElCol`「附件」:
```html
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="附件" label-width="85px">
<el-upload
:action="`${VITE_API_URL}/ticket/upload?scene=stock_orders`"
:headers="uploadHeaders"
:show-file-list="false"
:before-upload="(file) => beforeItemAttachmentUpload(file)"
:on-success="(res) => handleItemAttachmentUpload(res, idx)"
multiple
:accept="itemAttachmentAccept"
>
<el-button size="small" type="primary" plain>上传附件</el-button>
</el-upload>
<div class="item-attachment-tags" v-if="item.attachment_files?.length">
<el-tag
v-for="(f, fIdx) in item.attachment_files"
:key="fIdx"
closable
@close="removeItemAttachment(idx, fIdx)"
style="margin: 2px 4px 2px 0"
>
{{ f.name }}
</el-tag>
</div>
</ElFormItem>
</ElCol>
```
- `VITE_API_URL`、`uploadHeaders`(`useUploadHeaders`)、`beforeItemAttachmentUpload`(类型/大小校验,复用 `MultiFileUpload` 的 accept 与 10MB 限制)、`handleItemAttachmentUpload`、`removeItemAttachment` 均由 `useStockingForm` return,模板解构使用,与现有 `contractFiles` 等用法一致。
### 4.3 后端配合
- `stock_order_items.attachment` 字段已加(DB 就绪)。
- 接口层需对接:
- **store / update**:接收 `items[].attachment`(`file_name` 数组),写入 `stock_order_items.attachment`(按 json/序列化存储)。
- **show / detail**:返回 `items[].attachment`(`file_name` 数组,或含 `original_name`/`file_url` 的对象数组——见 4.4 约定)。
### 4.4 回填格式约定(需与后端对齐)
详情接口返回的 `items[].attachment` 有两种可能,择一与后端确认:
- **A**:`file_name` 字符串数组(如 `["xxx.pdf"]`)——前端回填时 `url` 需另行拼接,预览受限。
- **B(推荐)**:对象数组(如 `[{ original_name, file_url, file_name }]`,与 `/ticket/upload` 返回一致)——前端直接还原成 `attachment_files`,预览/下载可用。
实现(plan)阶段以前端实际请求详情接口的返回为准;若为 A,前端按现有 `populateFromDetail` 中文件字段的拼 url 方式处理。
## 5. 安全 / 限制
- 复用 `beforeUpload`:类型限制 `.jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.txt`,单文件 ≤ 10MB。
- 上传 header 带 token(`useUploadHeaders`),鉴权由后端 `/ticket/upload` 既有机制处理。
- `attachment` 只存 `file_name`,不含敏感信息。
## 6. 改动清单
| 端 | 文件 | 改动 |
|---|---|---|
| 前端 | `src/composables/useStockingForm.ts` | `ItemRow` 加 `attachment_files`;`newItem` 初始化;`mappedItems` 映射 `attachment`;`populateFromDetail` 回填;新增 `handleItemAttachmentUpload`/`removeItemAttachment`/`beforeItemAttachmentUpload`/暴露 `uploadHeaders` |
| 前端 | `src/views/crmpc/stocking/Add.vue` | 车辆明细行末尾加「附件」`ElCol`(`el-upload` + 标签列表);解构新方法 |
| 后端 | store/update/show 接口 | 对接 `items[].attachment`(DB 字段已就绪) |
## 7. 验收标准
1. 添加备货单:每辆车行有「上传附件」按钮,点选多文件逐个上传成功后,文件名以标签列出。
2. 标签可点 × 删除;删除后提交不包含该文件。
3. 提交备货单,Network 里 `items[].attachment` 为对应 `file_name` 数组;后端 `stock_order_items.attachment` 落库正确。
4. (后端 show 返回后)进入编辑/详情,已存附件能回显为标签列表。
5. 超格式 / 超 10MB 文件被拦截并提示。
## 8. 未做项 / 后续
- 本次只改 `Add.vue` 模板;`stocking_edit.vue` 共用 `useStockingForm`,数据层已支持,如需在编辑页也展示附件上传,后续加同款 `ElCol` 即可。
- 终版(final_add/final_edit)是否需要同等能力,待业务确认。