# kex-file-uploader 附件上传组件(uni-app **Vue3**):可选图片、PDF、Office 等任意文件,支持列表展示、进度上传、自定义 UI。 支持:**H5 / App(vue 页)/ 微信小程序 / 支付宝小程序 / 鸿蒙** 等主流端。 --- ## 目录 1. [功能一览](#功能一览) 2. [安装](#安装) 3. [三种用法怎么选](#三种用法怎么选) 4. [快速上手](#快速上手) 5. [完整示例](#完整示例) 6. [Props](#props) 7. [Events](#events) 8. [方法 ref](#方法-ref) 9. [插槽](#插槽) 10. [v-model 数据结构](#v-model-数据结构) 11. [上传与响应解析](#上传与响应解析) 12. [多端说明](#多端说明) 13. [常见问题](#常见问题) 14. [注意事项](#注意事项) 15. [版本](#版本) --- ## 功能一览 | 能力 | 说明 | | --- | --- | | 任意文件 | 图片 / 文档 / 压缩包等;`formats` 为空则不限制(后端不拦即可) | | 单次多选 | `multiple` 默认 `true`,受 `maxCount` 剩余名额限制 | | 内置列表 | 文件名、大小、进度、删除、重试、取消;图片缩略图 | | 自定义按钮 | `#trigger` 完全自定义,或 `show-trigger=false` + 外部按钮 | | 自定义列表 | `#item` 改每一行,或 `mode="headless"` 完全自绘 | | 自动 / 手动上传 | `autoUpload` + `uploadUrl`;也可 `ref.uploadAll()` | | 上传配置 | `uploadOptions`:`name` / `header` / `formData`(有默认值,只填 url 也能传) | | 并发 / 取消 | `concurrent`(默认 3)、`abort` / `abortAll` | | 响应解析 | `responseUrlKey` / `parseResponse` | | 只读 / 确认删 | `readonly`、`beforeRemove` | --- ## 安装 将插件拷贝到业务项目: ```text 你的项目/uni_modules/kex-file-uploader/ ``` uni-app **easycom** 会自动注册,页面里直接写标签,无需 `import`。 要求: - uni-app **Vue3** - App 请使用 **vue 页面**(不要用 nvue;App 选任意文件依赖 renderjs) 演示页(本示例工程):首页 → `kex-file-uploader`,或路径 `/pages/demos/file-uploader/index`。 --- ## 三种用法怎么选 | 场景 | 写法 | 内置按钮 | 内置列表 | | --- | --- | --- | --- | | 开箱即用 | 默认 `mode="list"` | ✅ | ✅ | | 按钮自己画,列表用默认 | `:show-trigger="false"` + `ref.choose()` | ❌ | ✅ | | 按钮和列表都自己画 | `mode="headless"` + `ref.choose()` + 自己 `v-for` | ❌ | ❌ | `show-trigger="false"` / `headless` 时,组件可放在页面任意位置,只要 `ref` 能调到即可。 **其它 Props(`uploadUrl`、`multiple`、`maxCount`、`formats` 等)在三种模式下都生效。** --- ## 快速上手 ### 1. 只选文件(不上传) ```vue ``` ### 2. 自动上传(最少只填接口) `name` / `header` / `formData` 有默认值,只配 `upload-url` 即可发起 `uni.uploadFile`。 ```vue ``` ### 3. 带 Token / 业务字段 ```vue ``` ### 4. 限制类型、大小、多选 ```vue ``` ### 5. 删除前确认 ```vue ``` ### 6. 只读回显 ```vue ``` ### 7. 自定义选择按钮(保留默认列表) ```vue ``` ### 8. 自定义列表行(保留组件结构) ```vue ``` ### 9. 外部打开 + 完全自定义列表(headless) 无内置按钮、无内置列表;组件可藏在页面任意处。 ```vue ``` ### 10. 手动上传(先选后传) ```vue ``` --- ## 完整示例 业务页常见写法(自动上传 + Token + 确认删除 + 事件): ```vue ``` 提交表单时,一般只取已成功项的远程地址: ```js const urls = files.value .filter((f) => f.status === 'success' && f.url) .map((f) => f.url) ``` --- ## Props | 属性 | 类型 | 默认 | 说明 | | --- | --- | --- | --- | | modelValue / v-model | `Array` | `[]` | 文件列表,见下方结构 | | mode | `String` | `'list'` | `list` 带列表 UI;`headless` 无 UI 纯能力 | | maxCount | `Number` | `9` | 列表最多文件数 | | multiple | `Boolean` | `true` | 单次能否多选;`false` 则每次只能选 1 个 | | maxSize | `Number` | `20` | 单文件上限 MB;`0` 表示不限制 | | formats | `String \| Array` | `''` | 允许扩展名,空=不限制。如 `'pdf,png'` 或 `['pdf','png']` | | fileType | `String` | `'all'` | 选文件类型提示:`all` / `image` / `video` / `file` | | accept | `String` | `''` | H5 / App 原生 input 的 accept,如 `image/*`、`.pdf,.doc` | | disabled | `Boolean` | `false` | 禁用选择 / 删除 / 上传 | | readonly | `Boolean` | `false` | 只读展示,不可选、不可删、不可传 | | showRemove | `Boolean` | `true` | 是否显示删除 | | showCount | `Boolean` | `true` | 默认按钮上是否显示数量 | | showThumb | `Boolean` | `true` | 图片是否显示缩略图 | | addText | `String` | `'添加附件'` | 默认选择按钮文案 | | showTrigger | `Boolean` | `true` | 是否显示内置选择按钮;`false` 时用外部 `ref.choose()` | | triggerPosition | `String` | `'bottom'` | 内置按钮位置:`bottom` / `top` | | addClass | `String \| Array \| Object` | `''` | 默认按钮额外 class | | addStyle | `Object \| String` | `{}` | 默认按钮内联样式 | | toast | `Boolean` | `true` | 超限等是否 toast | | autoUpload | `Boolean` | `false` | 选完是否自动上传 | | uploadUrl | `String` | `''` | 上传接口(对应 `uni.uploadFile` 的 url) | | uploadOptions | `Object` | 见下表 | 上传参数 | | concurrent | `Number` | `3` | 并发上传数;≤0 按 1 处理 | | responseUrlKey | `String` | `''` | 从响应取远程 url 的路径,如 `data.url`、`data.fileUrl` | | parseResponse | `Function` | `null` | `(raw, res) => url \| { url, data }`,优先级最高 | | beforeRemove | `Function` | `null` | `({ index, file }) => boolean \| Promise`,返回 `false` 拦截删除 | ### uploadOptions 对应 `uni.uploadFile`,未传字段自动补默认,**只填 `uploadUrl` 也能上传**: | 字段 | 默认 | 说明 | | --- | --- | --- | | name | `'file'` | 文件字段名 | | header | `{}` | 请求头(可用 computed 动态带 Token) | | formData | `{}` | 额外表单字段;**值必须是字符串** | | method | `'POST'` | App(Android/iOS)XHR 方法;其它端仍走 `uni.uploadFile`(POST) | | toBase | `false` | App:以 Base64 字符串作为字段值上传 | | withCredentials | `false` | App XHR 是否跨域携带 cookie(需服务端 CORS 允许 credentials) | ```js { name: 'file', header: { Authorization: 'Bearer xxx' }, formData: { bizId: '1', type: 'attach' }, method: 'POST', // toBase: true, // App 需要 Base64 字段时 withCredentials: true // iOS 本地测远程且要带 cookie } ``` 另有 props:`debug`(日志)、`distinct`(同名覆盖,默认重命名)。 ### 关于跨域(CORS) 跨域需服务端配置 CORS,组件侧可配合 `withCredentials`: - **Android/iOS App**:webview **XHR** → 需服务端 CORS。 - **鸿蒙 / H5 / 小程序**:`uni.uploadFile`,同样要服务端允许跨域。 - 带 cookie:`uploadOptions.withCredentials: true` + 服务端 `Access-Control-Allow-Credentials: true`(Origin 不能是 `*`)。 --- ## Events | 事件 | 触发时机 | 回调参数 | | --- | --- | --- | | update:modelValue | 列表变化 | `fileList`(同 v-model) | | change | 列表任意变化 | `{ fileList, ... }` | | select | 每次选文件完成 | 见下方 | | uploadSuccess | 单文件上传成功 | 见下方 | | uploadFail | 单文件上传失败 | `{ index, file, err, statusCode?, data?, res? }` | | uploadAbort | 取消上传 | `{ index, file }` | | remove | 删除(内置删除按钮) | `{ index, file }` | | oversize | 单文件超体积 | `{ name, path, size, maxSize }` | | exceed | 超过 maxCount | `{ maxCount }` | | preview | 点击预览 | `{ file }` | ### `@select` ```js { files, // 本次新增:与 v-model 单项同结构 count, // 本次新增数量 fileList // 当前全部 } ``` ### `@uploadSuccess` ```js { index, file, // 当前文件 url, // 优先为解析出的远程地址 statusCode, data, // 解析后的服务端 JSON(失败则为原始字符串) res, // 原始回调 fileList // 当前全部 } ``` ### `@change` ```js { fileList // 当前全部文件 } ``` --- ## 方法(ref) ```vue ``` | 方法 | 说明 | | --- | --- | | `choose()` | 打开系统选文件 | | `chooseFile({ success, fail, count, ... })` | 回调式选文件(可带 count/formats/size 等) | | `upload(index)` | 上传指定下标 | | `upload({ url, file, success, fail, onprogress, ... })` | 命令式上传单文件 | | `upload()` / `uploadAll()` | 上传所有 `ready` / `fail` 项(受 `concurrent` 控制) | | `abort(index)` | 取消指定上传中的项 | | `abortAll()` | 取消全部上传中 | | `clear()` | 清空列表(会先 abort) | | `getFileList()` | 返回当前列表副本 | | `openDocument({ file, success, fail })` | 打开文档预览 | | `getTempFilePath` / `getBase` / `getArrayBuffer` | 读临时路径 / Base64 / ArrayBuffer | ```js fu.value.choose() fu.value.uploadAll() fu.value.abort(0) fu.value.clear() console.log(fu.value.getFileList()) // 回调式选文件 / 命令式上传 fu.value.chooseFile({ count: 3, success: (files) => console.log(files) }) fu.value.upload({ url: 'https://xxx/upload', file: files[0], header: { Authorization: 'Bearer xxx' }, success: (e) => console.log(e.result), fail: console.error, onprogress: (e) => console.log(e.progress) }) ``` --- ## 插槽 | 插槽 | 说明 | 作用域参数 | | --- | --- | --- | | `trigger` | **完全自定义**选择按钮(推荐);达上限或 disabled/readonly 时不渲染 | `choose`、`count`、`maxCount`、`disabled`、`readonly` | | `add` | 只替换默认虚线框里的内容 | `count`、`maxCount` | | `item` | 完全自定义每一行列表 | `file`、`index`、`remove`、`retry`、`abort`、`preview` | 点击 `trigger` 里请调用插槽传入的 `choose()`(或外部 `ref.choose()`)。 --- ## v-model 数据结构 每一项大致为: ```js { uid: 'kex-fu-...', // 内部唯一 id name: '报告.pdf', url: 'https://...', // 成功后优先为远程地址;App 选中未上传时可能为空 path: '', // 本地路径(上传用;App 选任意文件时可能为空) size: 102400, // 字节 ext: 'pdf', status: 'success', // ready | uploading | success | fail progress: 100, // 0~100 message: '' // 失败等原因文案 } ``` | status | 含义 | | --- | --- | | ready | 已选中,待上传 | | uploading | 上传中 | | success | 成功(回显远程文件也是此状态) | | fail | 失败或已取消,可重试 | 说明: - App 选图会额外在内部保留 `thumb`(缩略图 dataURL)用于展示,**不会**把大段 base64 写进 `v-model` 的 `url` - 提交后端时建议只提交 `status === 'success'` 的 `url` / `name` 等业务字段 --- ## 上传与响应解析 底层:非 App 用 `uni.uploadFile`;App 用 renderjs + `XMLHttpRequest`。 成功条件:HTTP 状态码 `2xx`。 远程地址解析优先级: 1. **`parseResponse`**(最高) 2. **`responseUrlKey`**(如 `data.url`、`data.fileUrl`) 3. 内置字段:`url` / `path` / `data.url` / `data.path` / `data.fileUrl` / `result.url` ```vue ``` 也可在 `@uploadSuccess` 里用 `e.data` 自行处理,再改 `v-model`。 --- ## 多端说明 | 端 | 选文件方式 | 上传 | 多选 | | --- | --- | --- | --- | | H5 | `uni.chooseFile` | `uni.uploadFile` | ✅ | | 微信 / QQ | `chooseMessageFile`(聊天会话) | `uni.uploadFile` | ✅ | | 支付宝 | 图片相册 / 其它 `chooseFileFromDisk` | `uni.uploadFile` | 本地多为单选 | | **Android / iOS App** | renderjs `` | renderjs **XHR** | ✅(个别安卓管理器可能单选) | | **鸿蒙 App** | **`uni.chooseFile`**(**不走 renderjs**) | **`uni.uploadFile`** | 以系统能力为准 | 说明:uni-app 里 **`APP-PLUS` 不含鸿蒙**;鸿蒙走 `#ifndef APP-PLUS` 的选传链路。不要把「App = 必须 renderjs」理解成鸿蒙也要 renderjs。 微信选文件来自聊天记录、支付宝本地单选等,属于**平台能力限制**。 --- ## 常见问题 **Q:外部打开时,其它 props 还能用吗?** 能。`show-trigger="false"` / `headless` 只影响 UI,上传、限制、多选、事件等全部照常。 **Q:组件必须和按钮放在一起吗?** 不必。组件放页面任意位置(甚至视觉上藏起来),按钮在别处调 `ref.choose()` 即可。注意不要把组件 `v-if` 掉,否则 ref 为空。 **Q:为什么没有多选?** 确认 `multiple` 为 `true`(默认)且 `maxCount > 1`。支付宝本地文件、部分安卓系统选择器可能仍只能单选,可多次点选凑满。 **Q:App 真机图片只有「图」字、不能预览?** App 无本地 path 时用缩略图 + 内置全屏预览。需重新编译运行。 **Q:如何判断都传完了?** ```js const allDone = files.value.every( (f) => f.status === 'success' || f.status === 'fail' ) const allOk = files.value.length && files.value.every((f) => f.status === 'success') ``` **Q:控制台一直刷「uni统计 上报超时」?** 与本组件无关。在项目 `manifest.json` 中设置: ```json "uniStatistics": { "enable": false } ``` 然后重新编译。 --- ## 注意事项 1. **仅 Vue3** + `uni_modules` easycom。 2. **`formData` 的值必须是字符串**(uni 规范),如 `folderId: '123'`。 3. **自动上传**需同时:`autoUpload === true` 且 `uploadUrl` 非空。 4. **`mode="headless"`** 不渲染列表/按钮,请自行 `v-model` + `ref` 方法。 5. **Android/iOS App 用 vue 页**(renderjs),不要用 nvue;**鸿蒙**用 `uni.chooseFile`,无需 renderjs。 6. 删除:内置按钮走 `beforeRemove` + `@remove`;headless 下自己改数组时不会走 `beforeRemove`,需业务自行确认。 7. 各端对「任意文件」支持程度不同,以真机为准;`formats` 为空时组件不做类型拦截。 --- ## 版本 当前能力见同目录 [changelog.md](./changelog.md)。