zhangwencui
2026-04-30 36c5c131101409a06a14cda7af07a0886792d800
Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
已添加1个文件
480 ■■■■■ 文件已修改
FILE_UPLOAD_README.md 480 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FILE_UPLOAD_README.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,480 @@
# æœ¬åœ°æ–‡ä»¶ä¸Šä¼  README
本文档基于以下实现整理:
- `src/components/AttachmentUpload/file/index.vue`
- `src/components/AttachmentUpload/image/index.vue`
- `src/components/AttachmentPreview/image/index.vue`
- `src/components/Dialog/FileList.vue`
- `src/api/basicData/common.js`
- `src/api/publicApi/commonFile.js`
相关组件已在 `src/main.js` ä¸­æ³¨å†Œä¸ºå…¨å±€ç»„件,可直接在页面中使用:
- `FileUpload`
- `ImageUpload`
- `ImagePreview`
- `FileListDialog`
## 1. åŠŸèƒ½æ¦‚è§ˆ
当前这套上传能力主要分为 4 éƒ¨åˆ†ï¼š
1. `FileUpload`:普通文件上传,支持拖拽、批量上传、预览、删除
2. `ImageUpload`:图片上传,支持图片墙展示、预览、删除
3. `ImagePreview`:图片列表预览展示
4. `FileListDialog`:业务附件弹窗,支持查询、上传、删除、下载
上传底层统一走接口:
- `POST /common/upload`
对应方法在 `src/api/basicData/common.js`:
```js
uploadFile(data)
```
## 2. ä¸Šä¼ æŽ¥å£è¯´æ˜Ž
### 2.1 é€šç”¨ä¸Šä¼ æŽ¥å£
文件上传组件和图片上传组件都调用了:
```js
import { uploadFile } from '@/api/basicData/common'
```
接口特征:
- è¯·æ±‚方式:`POST`
- åœ°å€ï¼š`/common/upload`
- è¯·æ±‚类型:`multipart/form-data`
- æ”¯æŒ `FormData` æ‰¹é‡ä¸Šä¼ 
- é»˜è®¤å­—段名:`files`
组件内部会这样组装参数:
```js
const formData = new FormData()
validFiles.forEach((file) => {
  formData.append(props.uploadFieldName, file.raw)
})
```
### 2.2 ä¸Šä¼ è¿”回值要求
上传成功后,组件会尝试从以下结构中提取数组:
- `response`
- `response.data`
- `response.data.data`
- `response.payload`
- `response.payload.data`
- `response.rows`
- `response.result`
因此后端返回数组时,上面任意一种结构都可以被识别。
组件展示时常用到的字段有:
- æ–‡ä»¶åï¼š`name` / `originalFilename` / `fileName` / `uidFilename`
- æ–‡ä»¶åœ°å€ï¼š`url` / `downloadURL`
- å›¾ç‰‡åœ°å€ï¼š`url` / `previewURL` / `previewUrl`
- ä¸»é”®ï¼š`id`
建议上传接口返回的单项对象尽量包含:
```js
{
  id: 1,
  originalFilename: 'demo.pdf',
  downloadURL: 'https://xxx/demo.pdf',
  previewURL: 'https://xxx/demo.png'
}
```
## 3. FileUpload æ–‡ä»¶ä¸Šä¼ ç»„ä»¶
组件路径:
`src/components/AttachmentUpload/file/index.vue`
### 3.1 åŸºç¡€ç”¨æ³•
```vue
<template>
  <FileUpload v-model:file-list="fileList" />
</template>
<script setup>
import { ref } from 'vue'
const fileList = ref([])
</script>
```
### 3.2 å¸¸ç”¨å±žæ€§
| å±žæ€§ | è¯´æ˜Ž | ç±»åž‹ | é»˜è®¤å€¼ |
| --- | --- | --- | --- |
| `fileList` | ç»‘定文件列表 | `Array` | `[]` |
| `limit` | æœ€å¤§ä¸Šä¼ æ•°é‡ | `Number` | `10` |
| `fileSize` | å•个文件大小限制,单位 MB | `Number` | `50` |
| `fileType` | å…è®¸ä¸Šä¼ çš„æ–‡ä»¶ç±»åž‹ï¼Œå¦‚ `['pdf', 'docx']` | `Array` | `[]` |
| `buttonText` | ä¸Šä¼ æç¤ºæ–‡æ¡ˆ | `String` | `单击选择文件` |
| `disabled` | æ˜¯å¦ç¦ç”¨ | `Boolean` | `false` |
| `uploadFieldName` | `FormData` å­—段名 | `String` | `files` |
| `index` | è¡¨æ ¼/列表行模式下的当前行索引 | `Number` | `-1` |
| `childrenKey` | è¡Œå†…挂载字段名 | `String` | `files` |
### 3.3 äº‹ä»¶
| äº‹ä»¶ | è¯´æ˜Ž |
| --- | --- |
| `update:fileList` | æ–‡ä»¶åˆ—表变化时触发 |
| `change` | æ–‡ä»¶åˆ—表变化时触发,返回最新列表 |
### 3.4 é™åˆ¶è§„则
组件内已实现:
- æ–‡ä»¶æ•°é‡é™åˆ¶
- æ–‡ä»¶å¤§å°é™åˆ¶
- æ–‡ä»¶ç±»åž‹æ ¡éªŒ
- ä¸Šä¼ ä¸­çŠ¶æ€é”å®š
- å¤±è´¥åŽè‡ªåŠ¨æ¸…ç©ºå½“å‰é€‰æ‹©é˜Ÿåˆ—
例如限制 PDF/Word:
```vue
<FileUpload
  v-model:file-list="fileList"
  :limit="5"
  :file-size="20"
  :file-type="['pdf', 'doc', 'docx']"
/>
```
### 3.5 è¿”回数据格式建议
`FileUpload` æ›´é€‚合接收这样的列表:
```js
[
  {
    id: 1,
    originalFilename: '合同.pdf',
    downloadURL: 'https://xxx/contract.pdf'
  }
]
```
因为组件打开文件时会优先读取:
```js
url || downloadURL || previewURL || previewUrl
```
### 3.6 è¡Œå†…嵌套模式
如果上传组件放在表格某一行中,可配合 `index` å’Œ `childrenKey` ä½¿ç”¨ï¼š
```vue
<FileUpload
  v-model:file-list="tableData"
  :index="scope.$index"
  children-key="files"
/>
```
此时组件会自动读写:
```js
tableData[scope.$index].files
```
## 4. ImageUpload å›¾ç‰‡ä¸Šä¼ ç»„ä»¶
组件路径:
`src/components/AttachmentUpload/image/index.vue`
### 4.1 åŸºç¡€ç”¨æ³•
```vue
<template>
  <ImageUpload v-model:file-list="imageList" />
</template>
<script setup>
import { ref } from 'vue'
const imageList = ref([])
</script>
```
### 4.2 é»˜è®¤è¡Œä¸º
图片上传组件默认:
- æœ€å¤šä¸Šä¼  `10` å¼ 
- å•张不超过 `10MB`
- é»˜è®¤æ”¯æŒæ ¼å¼ï¼š`png / jpg / jpeg / webp`
- ä½¿ç”¨ `picture-card` é£Žæ ¼å±•示
- ç‚¹å‡»ç¼©ç•¥å›¾å¯é¢„览大图
### 4.3 å¸¸ç”¨ç¤ºä¾‹
```vue
<ImageUpload
  v-model:file-list="imageList"
  :limit="9"
  :file-size="5"
  :file-type="['png', 'jpg', 'jpeg']"
  button-text="上传图片"
/>
```
### 4.4 è¿”回数据格式建议
`ImageUpload` å±•示图片时优先读取:
```js
url || previewURL || previewUrl
```
建议后端返回:
```js
[
  {
    id: 1,
    originalFilename: '现场图片.jpg',
    previewURL: 'https://xxx/image.jpg'
  }
]
```
### 4.5 è¡Œå†…嵌套模式
图片组件同样支持行内字段写回:
```vue
<ImageUpload
  v-model:file-list="tableData"
  :index="scope.$index"
  children-key="images"
/>
```
默认写回字段为 `images`。
## 5. ImagePreview å›¾ç‰‡é¢„览组件
组件路径:
`src/components/AttachmentPreview/image/index.vue`
### 5.1 åŸºç¡€ç”¨æ³•
```vue
<ImagePreview :file-list="imageList" />
```
### 5.2 å¯é…ç½®é¡¹
| å±žæ€§ | è¯´æ˜Ž | ç±»åž‹ | é»˜è®¤å€¼ |
| --- | --- | --- | --- |
| `fileList` | å›¾ç‰‡åˆ—表 | `Array` | `[]` |
| `thumbSize` | ç¼©ç•¥å›¾å¤§å° | `Number` | `72` |
| `gap` | ç¼©ç•¥å›¾é—´è· | `Number` | `10` |
### 5.3 æ•°æ®è¦æ±‚
组件会过滤没有 `previewURL` çš„项,因此如果要正常显示,建议至少包含:
```js
[
  {
    previewURL: 'https://xxx/image.jpg',
    originalFilename: '图片1.jpg'
  }
]
```
如果列表为空,组件显示“暂无图片”。
## 6. FileListDialog é™„件弹窗组件
组件路径:
`src/components/Dialog/FileList.vue`
这个组件适合业务表单或详情页里的“附件管理”场景,能力包括:
- æ ¹æ®ä¸šåŠ¡ä¸»é”®æŸ¥è¯¢é™„ä»¶åˆ—è¡¨
- æ‰“开弹窗查看附件
- åœ¨å¼¹çª—中继续上传附件
- åˆ é™¤é™„ä»¶
- ä¸‹è½½é™„ä»¶
### 6.1 ç»„件属性
| å±žæ€§ | è¯´æ˜Ž | ç±»åž‹ | é»˜è®¤å€¼ |
| --- | --- | --- | --- |
| `visible` | æ˜¯å¦æ˜¾ç¤ºå¼¹çª— | `Boolean` | å¿…ä¼  |
| `recordType` | ä¸šåŠ¡ç±»åž‹ | `String` | `''` |
| `recordId` | ä¸šåС䏻键 | `Number` | `0` |
| `title` | å¼¹çª—标题 | `String` | `附件` |
| `width` | å¼¹çª—宽度 | `String` | `50%` |
| `showActions` | æ˜¯å¦æ˜¾ç¤ºä¸‹è½½/删除操作列 | `Boolean` | `true` |
### 6.2 åŸºç¡€ç”¨æ³•
```vue
<template>
  <el-button @click="visible = true">查看附件</el-button>
  <FileListDialog
    v-model:visible="visible"
    record-type="salesLedger"
    :record-id="rowId"
    title="附件列表"
  />
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
const rowId = ref(1001)
</script>
```
### 6.3 ç»„件内部依赖的接口
`FileListDialog` æœ¬èº«ä¸ç›´æŽ¥è°ƒç”¨ `commonFile.js`,而是依赖:
- `attachmentList`
- `createAttachment`
- `deleteAttachment`
处理逻辑为:
1. æ‰“开弹窗后根据 `recordType + recordId` æŸ¥è¯¢é™„ä»¶
2. ç‚¹å‡»â€œä¸Šä¼ é™„件”后,内部使用 `AttachmentUpload/file` å…ˆä¸Šä¼ åˆ° `/common/upload`
3. ä¸Šä¼ æˆåŠŸåŽï¼Œå°†è¿”å›žçš„æ–‡ä»¶å¯¹è±¡å’Œå·²æœ‰åˆ—è¡¨ä¸€èµ·æäº¤ç»™ `createAttachment`
4. åˆ é™¤æ—¶è°ƒç”¨ `deleteAttachment`
5. ä¸‹è½½æ—¶ç›´æŽ¥ `window.open(downloadURL, '_blank')`
因此这里要特别注意:
- `recordType` å’Œ `recordId` å¿…须是有效业务标识
- ä¸Šä¼ æˆåŠŸè¿”å›žçš„æ•°æ®ï¼Œéœ€è¦èƒ½è¢« `createAttachment` ç›´æŽ¥æŽ¥æ”¶
- åˆ—表中的下载地址字段应为 `downloadURL`
## 7. commonFile.js è¯´æ˜Ž
文件路径:
`src/api/publicApi/commonFile.js`
当前文件提供的是公共文件删除接口:
```js
delCommonFile(ids)
delCommonFileInvoiceLedger(ids)
```
对应接口:
- `/commonFile/delCommonFile`
- `/invoiceLedger/delFile`
这两个方法更适合已经和具体业务绑定后的“删除已保存附件”场景,不负责上传文件本身。
示例:
```js
import { delCommonFile } from '@/api/publicApi/commonFile'
await delCommonFile([1, 2, 3])
```
## 8. æŽ¨èä½¿ç”¨æ–¹å¼
### 8.1 æ™®é€šä¸šåŠ¡è¡¨å•ä¸Šä¼ é™„ä»¶
```vue
<FileUpload v-model:file-list="form.storageBlobDTOs" />
```
提交表单时直接带上:
```js
{
  ...form,
  storageBlobDTOs: form.storageBlobDTOs
}
```
### 8.2 å›¾ç‰‡ç±»ä¸šåŠ¡
```vue
<ImageUpload v-model:file-list="form.images" />
<ImagePreview :file-list="form.images" />
```
### 8.3 å·²è½åº“附件管理
```vue
<FileListDialog
  v-model:visible="dialogVisible"
  :record-type="recordType"
  :record-id="recordId"
/>
```
适合详情页、审批页、台账页这类“查看并维护当前业务附件”的场景。
## 9. æ³¨æ„äº‹é¡¹
1. `FileUpload` å’Œ `ImageUpload` åªæ˜¯è´Ÿè´£æŠŠæ–‡ä»¶å…ˆä¼ åˆ° `/common/upload`,不等于已经和业务数据绑定。
2. å¦‚果业务需要持久化附件关系,仍需要在保存表单时把返回的文件对象提交给业务接口。
3. `ImagePreview` å½“前只识别 `previewURL`,如果后端只返回 `url`,预览组件将不会展示,最好统一补齐 `previewURL`。
4. `FileListDialog` ä¾èµ– `recordType`、`recordId` æŸ¥è¯¢å’Œä¿å­˜é™„件关系,新增业务时要先确认后端关联接口可用。
5. åˆ é™¤æœ¬åœ°åˆ—表项和删除已保存附件是两件事:
   - ä¸Šä¼ ç»„件里的删除:只会从当前前端绑定数组中移除
   - `commonFile.js` / `deleteAttachment`:才是真正调用后端删除
## 10. ä¸€å¥—最常见的页面写法
```vue
<template>
  <el-form :model="form">
    <el-form-item label="附件">
      <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="5" />
    </el-form-item>
    <el-form-item label="现场图片">
      <ImageUpload v-model:file-list="form.images" :limit="9" />
    </el-form-item>
    <el-form-item label="图片预览">
      <ImagePreview :file-list="form.images" />
    </el-form-item>
  </el-form>
</template>
<script setup>
import { ref } from 'vue'
const form = ref({
  storageBlobDTOs: [],
  images: [],
})
</script>
```
如果你的目标是“先上传,再跟业务一起保存”,这套写法可以直接作为基础模板使用。