更新记录

1.2.0(2026-07-16) 下载此版本

  • 支持鸿蒙:uni.chooseFile + uni.uploadFile(非 renderjs);package.json 标记 harmony √
  • 新增:debug / distinctuploadOptions.method / toBase、JSON body、withCredentials
  • 新增:chooseFile、命令式 upload({url,file,...})openDocumentgetTempFilePath / getBase / getArrayBuffer
  • 文档:多端表拆分 Android/iOS vs 鸿蒙

平台兼容性

uni-app(3.8.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
-
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - -

kex-file-uploader

附件上传组件(uni-app Vue3):可选图片、PDF、Office 等任意文件,支持列表展示、进度上传、自定义 UI。

支持:H5 / App(vue 页)/ 微信小程序 / 支付宝小程序 / 鸿蒙 等主流端。


目录

  1. 功能一览
  2. 安装
  3. 三种用法怎么选
  4. 快速上手
  5. 完整示例
  6. Props
  7. Events
  8. 方法 ref
  9. 插槽
  10. v-model 数据结构
  11. 上传与响应解析
  12. 多端说明
  13. 常见问题
  14. 注意事项
  15. 版本

功能一览

能力 说明
任意文件 图片 / 文档 / 压缩包等;formats 为空则不限制(后端不拦即可)
单次多选 multiple 默认 true,受 maxCount 剩余名额限制
内置列表 文件名、大小、进度、删除、重试、取消;图片缩略图
自定义按钮 #trigger 完全自定义,或 show-trigger=false + 外部按钮
自定义列表 #item 改每一行,或 mode="headless" 完全自绘
自动 / 手动上传 autoUpload + uploadUrl;也可 ref.uploadAll()
上传配置 uploadOptionsname / header / formData(有默认值,只填 url 也能传)
并发 / 取消 concurrent(默认 3)、abort / abortAll
响应解析 responseUrlKey / parseResponse
只读 / 确认删 readonlybeforeRemove

安装

将插件拷贝到业务项目:

你的项目/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(uploadUrlmultiplemaxCountformats 等)在三种模式下都生效。


快速上手

1. 只选文件(不上传)

<template>
  <kex-file-uploader v-model="files" :max-count="5" />
</template>

<script setup>
import { ref } from 'vue'
const files = ref([])
</script>

2. 自动上传(最少只填接口)

name / header / formData 有默认值,只配 upload-url 即可发起 uni.uploadFile

<template>
  <kex-file-uploader
    v-model="files"
    :auto-upload="true"
    upload-url="https://example.com/api/upload"
    response-url-key="data.url"
    @uploadSuccess="onOk"
    @uploadFail="onFail"
  />
</template>

<script setup>
import { ref } from 'vue'
const files = ref([])
const onOk = (e) => console.log('成功', e.url, e.data)
const onFail = (e) => console.log('失败', e.err)
</script>

3. 带 Token / 业务字段

<script setup>
import { ref, computed } from 'vue'

const files = ref([])
const token = ref('Bearer xxx')

const uploadOptions = computed(() => ({
  // name 默认 'file',可不写
  header: { Authorization: token.value },
  // 值必须是字符串
  formData: { folderId: '123', module: 'attach' }
}))
</script>

<template>
  <kex-file-uploader
    v-model="files"
    :auto-upload="true"
    upload-url="https://example.com/api/upload"
    :upload-options="uploadOptions"
  />
</template>

4. 限制类型、大小、多选

<!-- 单次可多选,列表最多 9 个;仅 pdf/word;单文件 ≤ 10MB -->
<kex-file-uploader
  v-model="files"
  :max-count="9"
  :multiple="true"
  :max-size="10"
  formats="pdf,doc,docx"
/>

<!-- 单次只能选 1 个,仍可多次点选直到 maxCount -->
<kex-file-uploader v-model="files" :max-count="9" :multiple="false" />

5. 删除前确认

<script setup>
const beforeRemove = ({ file }) =>
  new Promise((resolve) => {
    uni.showModal({
      title: '确认删除',
      content: `删除「${file.name}」?`,
      success: (r) => resolve(!!r.confirm),
      fail: () => resolve(false)
    })
  })
</script>

<template>
  <kex-file-uploader v-model="files" :before-remove="beforeRemove" />
</template>

6. 只读回显

<script setup>
const files = ref([
  { name: '合同.pdf', url: 'https://cdn.example.com/a.pdf', size: 102400, status: 'success' }
])
</script>

<template>
  <kex-file-uploader v-model="files" :readonly="true" />
</template>

7. 自定义选择按钮(保留默认列表)

<!-- 方式 A:插槽完全替换按钮 UI -->
<kex-file-uploader v-model="files" :max-count="5">
  <template #trigger="{ choose, count, maxCount }">
    <view class="my-btn" @click="choose">上传材料 {{ count }}/{{ maxCount }}</view>
  </template>
</kex-file-uploader>

<!-- 方式 B:隐藏内置按钮,外部任意位置调用 -->
<kex-file-uploader ref="fu" v-model="files" :show-trigger="false" />
<button @click="fu.choose()">选择附件</button>

8. 自定义列表行(保留组件结构)

<kex-file-uploader v-model="files">
  <template #item="{ file, remove, retry, abort, preview }">
    <view @click="preview">
      <text>{{ file.name }} · {{ file.status }}</text>
      <text v-if="file.status === 'uploading'" @click.stop="abort">取消</text>
      <text v-else-if="file.status === 'fail'" @click.stop="retry">重试</text>
      <text v-else @click.stop="remove">删除</text>
    </view>
  </template>
</kex-file-uploader>

9. 外部打开 + 完全自定义列表(headless)

无内置按钮、无内置列表;组件可藏在页面任意处。

<template>
  <kex-file-uploader
    ref="fu"
    v-model="files"
    mode="headless"
    :max-count="9"
    :multiple="true"
    :auto-upload="true"
    upload-url="https://example.com/api/upload"
    :upload-options="uploadOptions"
    @select=""
    @uploadSuccess="onOk"
    @uploadFail="onFail"
  />

  <button @click="fu.choose()">选择附件</button>
  <button @click="fu.uploadAll()">上传全部</button>
  <button @click="fu.clear()">清空</button>

  <view v-for="(f, i) in files" :key="f.uid || i">
    <text>{{ f.name }} · {{ f.status }} {{ f.progress }}%</text>
    <text v-if="f.status === 'uploading'" @click="fu.abort(i)">取消</text>
    <text v-else-if="f.status === 'fail'" @click="fu.upload(i)">重试</text>
    <text @click="files.splice(i, 1)">删除</text>
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'

const fu = ref(null)
const files = ref([])
const uploadOptions = computed(() => ({
  header: { Authorization: 'Bearer xxx' }
}))

const  = (e) => console.log('选中', e.count, e.fileList)
const onOk = (e) => console.log('成功', e.url, e.data)
const onFail = (e) => console.log('失败', e.err)
</script>

10. 手动上传(先选后传)

<template>
  <kex-file-uploader
    ref="fu"
    v-model="files"
    :auto-upload="false"
    upload-url="https://example.com/api/upload"
  />
  <button @click="fu.uploadAll()">开始上传</button>
</template>

完整示例

业务页常见写法(自动上传 + Token + 确认删除 + 事件):

<template>
  <kex-file-uploader
    v-model="files"
    :max-count="9"
    :max-size="20"
    :multiple="true"
    formats=""
    :auto-upload="true"
    upload-url="https://example.com/api/upload"
    :upload-options="uploadOptions"
    response-url-key="data.url"
    :concurrent="3"
    :before-remove="beforeRemove"
    @select=""
    @change="onChange"
    @uploadSuccess="onOk"
    @uploadFail="onFail"
    @uploadAbort=""
    @remove="onRemove"
  />
</template>

<script setup>
import { ref, computed } from 'vue'

const files = ref([])
const token = ref('') // 从登录态取值

const uploadOptions = computed(() => ({
  name: 'file',
  header: token.value ? { Authorization: token.value } : {},
  formData: { module: 'attachment' }
}))

const beforeRemove = ({ file }) =>
  new Promise((resolve) => {
    uni.showModal({
      title: '确认删除',
      content: `删除「${file.name}」?`,
      success: (r) => resolve(!!r.confirm)
    })
  })

const  = (e) => console.log('选图', e.count)
const onChange = (e) => console.log('列表', e.fileList)
const onOk = (e) => console.log('成功', e.url, e.data)
const onFail = (e) => console.log('失败', e.err)
const  = (e) => console.log('取消', e.file)
const onRemove = (e) => console.log('删除', e.index, e.file)
</script>

提交表单时,一般只取已成功项的远程地址:

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.urldata.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)
{
  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

{
  files,     // 本次新增:与 v-model 单项同结构
  count,     // 本次新增数量
  fileList   // 当前全部
}

@uploadSuccess

{
  index,
  file,        // 当前文件
  url,         // 优先为解析出的远程地址
  statusCode,
  data,        // 解析后的服务端 JSON(失败则为原始字符串)
  res,         // 原始回调
  fileList     // 当前全部
}

@change

{
  fileList   // 当前全部文件
}

方法(ref)

<kex-file-uploader ref="fu" v-model="files" upload-url="..." />
方法 说明
choose() 打开系统选文件
chooseFile({ success, fail, count, ... }) 回调式选文件(可带 count/formats/size 等)
upload(index) 上传指定下标
upload({ url, file, success, fail, , ... }) 命令式上传单文件
upload() / uploadAll() 上传所有 ready / fail 项(受 concurrent 控制)
abort(index) 取消指定上传中的项
abortAll() 取消全部上传中
clear() 清空列表(会先 abort)
getFileList() 返回当前列表副本
openDocument({ file, success, fail }) 打开文档预览
getTempFilePath / getBase / getArrayBuffer 读临时路径 / Base64 / ArrayBuffer
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,
  : (e) => console.log(e.progress)
})

插槽

插槽 说明 作用域参数
trigger 完全自定义选择按钮(推荐);达上限或 disabled/readonly 时不渲染 choosecountmaxCountdisabledreadonly
add 只替换默认虚线框里的内容 countmaxCount
item 完全自定义每一行列表 fileindexremoveretryabortpreview

点击 trigger 里请调用插槽传入的 choose()(或外部 ref.choose())。


v-model 数据结构

每一项大致为:

{
  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-modelurl
  • 提交后端时建议只提交 status === 'success'url / name 等业务字段

上传与响应解析

底层:非 App 用 uni.uploadFile;App 用 renderjs + XMLHttpRequest

成功条件:HTTP 状态码 2xx

远程地址解析优先级:

  1. parseResponse(最高)
  2. responseUrlKey(如 data.urldata.fileUrl
  3. 内置字段:url / path / data.url / data.path / data.fileUrl / result.url
<!-- 方式 A:路径取值 -->
<kex-file-uploader response-url-key="data.url" ... />

<!-- 方式 B:自定义函数 -->
<kex-file-uploader
  :parse-response="(raw) => {
    const d = typeof raw === 'string' ? JSON.parse(raw) : raw
    return (d && d.data && d.data.fileUrl) || ''
  }"
  ...
/>

也可在 @uploadSuccess 里用 e.data 自行处理,再改 v-model


多端说明

选文件方式 上传 多选
H5 uni.chooseFile uni.uploadFile
微信 / QQ chooseMessageFile(聊天会话) uni.uploadFile
支付宝 图片相册 / 其它 chooseFileFromDisk uni.uploadFile 本地多为单选
Android / iOS App renderjs <input type="file"> 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:为什么没有多选?
确认 multipletrue(默认)且 maxCount > 1。支付宝本地文件、部分安卓系统选择器可能仍只能单选,可多次点选凑满。

Q:App 真机图片只有「图」字、不能预览?
App 无本地 path 时用缩略图 + 内置全屏预览。需重新编译运行。

Q:如何判断都传完了?

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 中设置:

"uniStatistics": { "enable": false }

然后重新编译。


注意事项

  1. 仅 Vue3 + uni_modules easycom。
  2. formData 的值必须是字符串(uni 规范),如 folderId: '123'
  3. 自动上传需同时:autoUpload === trueuploadUrl 非空。
  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

隐私、权限声明

1. 本插件需要申请的系统权限列表:

文件系统/相册(选文件时由系统授权)

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。