Co-Authored-By: Claude <noreply@anthropic.com>
8.8 KiB
备货单车辆明细附件上传 实现计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 在 Add.vue 车辆明细每行增加行内附件上传,附件随车辆提交到 stock_order_items.attachment。
Architecture: 数据层改共用的 useStockingForm.ts:ItemRow 加 attachment_files(展示用 FileItem[])、newItem 初始化、mappedItems 映射成 attachment({url, original_name} 数组,与 contract 实际格式一致)、新增上传/删除方法并暴露 uploadHeaders/itemUploadAction。UI 在 Add.vue 明细行加一个 ElCol:el-upload 按钮 + 已传文件 el-tag 列表。上传走现成 /ticket/upload?scene=stock_orders,无需改上传接口。
Tech Stack: Vue 3 <script setup>、TypeScript、element-plus 2.8、useStockingForm composable、useUploadHeaders(已导入)。
Global Constraints
- 无前端测试框架:验收 =
npm run build(含vue-tsc --noEmit类型检查)通过 +dev手动核对。 - attachment 提交格式:
{ url, original_name }对象数组(参照baseForm.contract实际代码 useStockingForm.ts:697-700),不是 file_name 数组——这样回填/预览有 url 可用。 useUploadHeaders已在 useStockingForm.ts:6 导入、:155 取出uploadHeaders,直接复用、暴露。- 后端
stock_order_items.attachment字段已加(DB 就绪);接口层需对接veh_list[].attachment的接收与返回。 - commit 规范:每任务一次 commit,
feat:开头。
文件结构
| 文件 | 责任 | 改动 |
|---|---|---|
src/composables/useStockingForm.ts |
车辆表单数据/上传方法(Add/edit 共用) | Modify |
src/views/crmpc/stocking/Add.vue |
添加备货单页,车辆明细行内附件 UI | Modify |
Task 1: useStockingForm.ts 数据层 + 上传方法
Files:
- Modify:
src/composables/useStockingForm.ts(ItemRow接口 ~49、newItem~218、上传方法区 ~706、mappedItems~1234、populateFromDetailitems 映射 ~974、return~1515)
Interfaces:
-
Consumes:
useUploadHeaders(已导入)、items: ref<ItemRow[]>(已有)、/ticket/upload返回{code, data:{original_name, file_url, file_name}}。 -
Produces:
ItemRow.attachment_files;暴露uploadHeaders/itemUploadAction/beforeItemAttachmentUpload/handleItemAttachmentUpload/removeItemAttachment。 -
Step 1:
ItemRow接口加字段
在 export interface ItemRow { ... } 内(如 remark: string 之后)加:
attachment_files?: { name: string; url: string; file_name: string }[]
- Step 2:
newItem()初始化
在 const newItem = (): ItemRow => ({ ... }) 对象内(如 remark: '' 之后)加:
attachment_files: [],
- Step 3: 新增上传 action 与方法
在 handleContractUpload 之后(约 line 706 后)插入:
// 车辆明细附件上传
const itemUploadAction = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=stock_orders`
const beforeItemAttachmentUpload = (file: File) => {
const isValidType = /\.(jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx|txt)$/i.test(file.name)
if (!isValidType) {
ElMessage.error('只能上传图片或文档文件!')
return false
}
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB!')
return false
}
return true
}
const handleItemAttachmentUpload = (response: any, idx: number) => {
if (response.code === 200) {
const fileInfo = {
name: response.data.original_name || '附件',
url: response.data.file_url,
file_name: response.data.file_name
}
if (!items.value[idx].attachment_files) {
items.value[idx].attachment_files = []
}
items.value[idx].attachment_files!.push(fileInfo)
ElMessage.success('附件上传成功!')
} else {
ElMessage.error(response.message || '附件上传失败!')
}
}
const removeItemAttachment = (idx: number, fileIdx: number) => {
const list = items.value[idx].attachment_files
if (list) {
list.splice(fileIdx, 1)
ElMessage.success('附件已删除')
}
}
- Step 4:
mappedItems映射attachment
在 mappedItems 的 increase_price: wrapMoney(...) 之后(约 line 1234 后、})) 之前)加:
attachment: (it.attachment_files || []).map((f) => ({
url: f.url,
original_name: f.name
}))
- Step 5:
populateFromDetail回填
在 populateFromDetail 中 items.value = list.map((it: any) => ({ ... })) 的映射对象内(与 model_snapshot 等同层)加,兼容后端两种返回格式:
attachment_files: (it.attachment || []).map((f: any) => ({
name: f.original_name || f.name || '附件',
url: f.url || f.file_url || '',
file_name: f.file_name || f.url || ''
}))
- Step 6:
return暴露
在 return {(约 line 1515)对象内加:
uploadHeaders,
itemUploadAction,
beforeItemAttachmentUpload,
handleItemAttachmentUpload,
removeItemAttachment,
- Step 7: 类型检查(阶段性)
Run: npx vue-tsc --noEmit 2>&1 | grep -E "useStockingForm|error TS" | head
Expected: 无 useStockingForm.ts 相关 error。(dayjs 等无关报错忽略)
- Step 8: Commit
git add src/composables/useStockingForm.ts
git commit -m "feat(stocking): useStockingForm 支持车辆明细附件上传
ItemRow 增加 attachment_files,mappedItems 映射 attachment,
新增车辆附件上传/删除方法。
Co-Authored-By: Claude <noreply@anthropic.com>"
Task 2: Add.vue 行内附件 UI
Files:
- Modify:
src/views/crmpc/stocking/Add.vue(解构 ~1189-1211、车辆明细行 ~116+)
Interfaces:
-
Consumes: Task 1 暴露的
uploadHeaders/itemUploadAction/beforeItemAttachmentUpload/handleItemAttachmentUpload/removeItemAttachment;items、idx(已有)。 -
Produces: 每行附件上传交互。
-
Step 1: 解构新方法
在 Add.vue 从 useStockingForm(false) 解构的块内(chooseVehicle 等同处,约 line 1206-1210)加:
uploadHeaders,
itemUploadAction,
beforeItemAttachmentUpload,
handleItemAttachmentUpload,
removeItemAttachment
- Step 2: 明细行加「附件」ElCol
在车辆明细 v-for="(item, idx) in items" 的行内(建议放在「里程」ElCol 之后,约 line 196 后)加:
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="附件" label-width="85px">
<el-upload
:action="itemUploadAction"
:headers="uploadHeaders"
:show-file-list="false"
:before-upload="beforeItemAttachmentUpload"
:on-success="(res: any) => handleItemAttachmentUpload(res, idx)"
multiple
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.txt"
>
<el-button size="small" type="primary" plain>上传附件</el-button>
</el-upload>
<div
v-if="item.attachment_files && item.attachment_files.length"
style="margin-top: 4px"
>
<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>
- Step 3: 类型检查 + 构建
Run: npm run build
Expected: vue-tsc 无 error,vite build 产出 dist。
- Step 4: 手动核对(dev)
Run: npm run dev,进入添加备货单页。
核对:每辆车行有「上传附件」按钮;选文件上传后出现文件名 tag;tag × 可删;提交时 Network veh_list[].attachment 为 {url, original_name} 数组。
- Step 5: Commit
git add src/views/crmpc/stocking/Add.vue
git commit -m "feat(stocking): 添加备货单车辆明细行内附件上传
Co-Authored-By: Claude <noreply@anthropic.com>"
后端联调清单(交付后端)
stock_order_items.attachment字段已就绪。- store/update:接收
veh_list[].attachment({url, original_name}对象数组),写入attachment(json 序列化)。 - show/detail:返回
items[].attachment(同{url, original_name}结构),前端据此回显。