npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mxyu/uploader

v0.1.0

Published

生产级 Vue 3 文件上传组件库:点击/拖拽/粘贴、分片上传、断点续传、并发调度

Readme

@mxyu/uploader

生产级 Vue 3 文件上传组件库:点击 / 拖拽 / 粘贴、单/多文件、文件夹、进度、取消、重试、分片上传、断点续传。 提供开箱即用的 <FileUploader> 组件,也提供 headless 的 useUploader 组合式函数。

安装

pnpm add @mxyu/uploader
# 或 npm install @mxyu/uploader

依赖 vue >= 3(peerDependency)。内置 spark-md5 用于分片哈希。

快速开始(组件用法)

<script setup lang="ts">
import { FileUploader } from '@mxyu/uploader';
</script>

<template>
  <FileUploader
    action="/api/upload"
    multiple
    accept="image/*,.pdf"
    :max-size="50 * 1024 * 1024"
    @success="(res, file) => console.log('上传成功', file.name, file.url)"
    @error="(err, file) => console.error('上传失败', file.name, err)"
  />
</template>

Headless 用法(useUploader)

不需要内置 UI 时,直接用组合式函数自行渲染:

import { useUploader } from '@mxyu/uploader';

const uploader = useUploader({
  action: '/api/upload',
  maxConcurrent: 3,
  onSuccess: (res, file) => console.log(file.url),
});

// uploader.fileList / isUploading / totalProgress 均为响应式
// uploader.addFiles(fileList) / submit() / cancel() / retry(uid) ...

Props

| 名称 | 类型 | 默认 | 说明 | |---|---|---|---| | action | string | — | 上传接口 URL,与 customRequest 互斥(后者优先) | | customRequest | (opts) => AbortController \| void | — | 完全自定义上传(OSS/S3 直传、签名) | | responseHandler | (response) => string \| undefined | 取 response.url ?? response.data.url | 从响应提取文件地址写入 file.url | | disabled | boolean | false | 禁用 | | multiple | boolean | false | 多文件 | | directory | boolean | false | 文件夹上传(webkitdirectory,自动视为多文件) | | accept | string | — | 类型限制,如 "image/*,.pdf" | | pasteEnabled | boolean | true | 启用粘贴上传 | | includeHiddenFiles | boolean | false | 文件夹上传是否含隐藏文件 | | duplicateStrategy | 'replace' \| 'ignore' \| 'prompt' | 'replace' | 重复文件策略(name+size+lastModified 判重) | | beforeUpload | (file, list) => boolean \| File \| undefined \| Promise<...> | — | 上传前钩子(压缩/水印/格式转换) | | maxSize | number | — | 单文件最大字节数(超出 → invalid) | | maxCount | number | — | 列表最大数量(超出 → exceed 事件) | | validator | (file) => boolean \| string \| Promise<...> | — | 自定义校验 | | autoUpload | boolean | true | 入队后自动上传;false 需调用 submit() | | withCredentials | boolean | false | 跨域携带凭证 | | headers | Record<string,string> | — | 自定义请求头 | | data | object \| (file) => object | — | 附加表单字段 | | name | string | 'file' | FormData 文件字段名 | | requestTimeout | number | 0 | 请求超时(毫秒),0 不超时 | | maxRetry | number | 3 | 单文件最大重试次数 | | maxConcurrent | number | 3 | 文件级并发数 | | chunkConcurrent | number | 5 | 单文件分片并发数 | | chunkSize | number | 2MB | 每片大小 | | chunkThreshold | number | — | 超过该字节数启用分片;不设则不分片 | | checkChunks | (fileHash) => Promise<number[]> | — | 断点续传:返回已完成分片索引 | | mergeChunks | (uploadId, totalChunks, fileHash) => Promise<unknown> | — | 通知服务端合并分片 | | dragEnabled | boolean | true | 启用拖拽 | | showList | boolean | true | 显示文件列表 | | showGlobalProgress | boolean | true | 显示聚合进度 | | limit | number | — | 列表最大渲染条目数 |

Events

| 事件 | 参数 | 说明 | |---|---|---| | change | (file, fileList) | 任意状态变更 | | progress | (percent, file, fileList) | 进度更新 | | success | (response, file, fileList) | 单文件成功 | | error | (error, file, fileList) | 网络/服务端失败(不含校验失败) | | invalid | (file, reason) | 校验失败 / beforeUpload 拒绝 | | cancel | (file \| null, fileList) | 取消(null 表示全部) | | duplicate | (file, fileList) | duplicateStrategy='prompt' 时触发 | | exceed | (files, fileList) | maxCount 溢出 | | drop | (files) | 拖拽投放(纯通知) | | paste | (files) | 粘贴(纯通知) | | all-done | (fileList) | 全部文件达终态 |

Slots

| 插槽 | 作用域 | 说明 | |---|---|---| | default | — | 替换整个上传区域 | | trigger | { isDragging, disabled } | 替换触发器内容 | | file-item | { file } | 完全自定义单行 | | file-icon | { file } | 自定义图标 | | file-extra | { file } | 文件行末尾扩展区 | | actions | { fileList, submit, cancel, clearFiles } | 全局操作区 | | empty | — | 空列表占位 |

Expose(通过模板 ref 调用)

const uploaderRef = ref();
uploaderRef.value.submit();          // 提交全部 queued(autoUpload:false 时)
uploaderRef.value.cancel(uid?);      // 取消单个/全部
uploaderRef.value.retry(uid);        // 重试
uploaderRef.value.clearFiles();      // 清除终态文件
uploaderRef.value.clearAll();        // 清空全部
uploaderRef.value.getFileList();     // 当前文件列表只读副本

customRequest(OSS / S3 直传)

function customRequest(opts: CustomRequestOptions) {
  const controller = new AbortController();
  fetch(presignedUrl, { method: 'PUT', body: opts.file.raw, signal: controller.signal })
    .then(() => opts.onSuccess({ url: publicUrl }))
    .catch(opts.onError);
  return { abort: () => controller.abort() };
}

分片上传与断点续传

设置 chunkThreshold 后,超过阈值的文件自动走分片上传:

<FileUploader
  action="/api/upload-chunk"
  :chunk-threshold="100 * 1024 * 1024"
  :chunk-size="2 * 1024 * 1024"
  :check-chunks="hash => fetch(`/api/uploaded?hash=${hash}`).then(r => r.json())"
  :merge-chunks="(uploadId, total, hash) => fetch('/api/merge', { method: 'POST', body: JSON.stringify({ uploadId, total, hash }) }).then(r => r.json())"
/>

后端契约(默认分片实现):每个分片以 POST {action} 发送 FormData,字段包含: uploadIdfileHashchunkIndextotalChunks,以及文件字段(name 指定,默认 file)的分片 Blob。 其中 uploadId === fileHash(内容寻址,本库未约定独立的 init 接口)。服务端按 fileHash 归并; checkChunks(fileHash) 返回已完成分片索引用于续传;mergeChunks 合并并返回最终响应(经 responseHandler 提取 url)。

主题定制(CSS 变量)

组件样式通过 CSS 变量覆盖,例如:

.mxu-uploader {
  --mxu-color-primary: #1677ff;
  --mxu-color-uploading: #1677ff;
  --mxu-color-done: #52c41a;
  --mxu-color-error: #ff4d4f;
  --mxu-color-paused: #fa8c16;
}

状态机

pending → validating →(invalid | processing | queued) → uploading →(paused | done | error | cancelled)invalid 为校验失败终态;error 为网络失败,retryCount < maxRetry 时可重试,否则等同终态。

FAQ

  • 进度一直停在 99%? 分片上传在全部分片完成后、合并成功前锁定 99%,合并成功后置 100%。
  • 哈希在哪里计算? 当前版本在主线程增量计算(MD5);useUploader 支持注入自定义 computeHash(如 Worker 实现)。
  • 取消和失败如何区分? 用户主动取消 → cancelled(不计入重试);网络/服务端失败 → error(按指数退避自动重试)。
  • React 支持? 当前为 Vue 3 组件库;框架无关的底层能力(computeFileHashuploadFileInChunkscreateDefaultExecutor)可直接在任意框架使用,React demo 在后续迭代提供。

License

MIT