开发预览 更新于 2026-05-10

Dropzone 文件拖拽上传

拖拽 + 点击双触发的文件选择器,内置文件列表、进度条与 reject 回调。

基础用法

v-model 绑定一个 File[]。把文件拖进虚线框,或点击区域弹出文件对话框;选完会出现一个紧凑的文件列表,每行带尺寸和移除按钮。

背景
点击或拖拽文件到此区域上传
支持拖入或点击选择,多文件均可
<script setup lang="ts">
import { ref } from 'vue';
import { CfDropzone } from '@chufix-design/vue';

const files = ref<File[]>([]);
</script>

<template>
  <CfDropzone
    v-model="files"
    hint="支持拖入或点击选择,多文件均可"
  />
</template>
<script setup>
import { ref } from 'vue';
import { CfDropzone } from '@chufix-design/vue';

const files = ref<File[]>([]);
</script>

<template>
  <CfDropzone
    v-model="files"
    hint="支持拖入或点击选择,多文件均可"
  />
</template>
import { useState } from 'react';
import { CfDropzone } from '@chufix-design/react';

export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
return (
  <CfDropzone
    value={files}
    hint="支持拖入或点击选择,多文件均可"
    onChange={setFiles}
  />
);
}
import { useState } from 'react';
import { CfDropzone } from '@chufix-design/react';

export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
return (
  <CfDropzone
    value={files}
    hint="支持拖入或点击选择,多文件均可"
    onChange={setFiles}
  />
);
}

约束 + 拒绝回调

accept / maxSize / maxFiles 控制接受范围;任何被拒绝的文件都会通过 reject(Vue)/ onReject(React)回调出来,附带具体原因('too-large' | 'too-many' | 'wrong-type' | 'duplicate')。

背景
点击或拖拽文件到此区域上传
只接受图片,单文件最大 1 MB,最多 3 个
<script setup lang="ts">
import { ref } from 'vue';
import { CfDropzone, type DropzoneRejection } from '@chufix-design/vue';

const files = ref<File[]>([]);
const reasons: Record<string, string> = {
  'too-large': '超出 1 MB',
  'too-many': '最多 3 个',
  'wrong-type': '只接受图片',
  duplicate: '重复',
};
const errors = ref<string[]>([]);

function onReject(items: DropzoneRejection[]) {
  errors.value = items.map((it) => `${it.file.name} · ${reasons[it.reason]}`);
  setTimeout(() => (errors.value = []), 4000);
}
</script>

<template>
  <div style="display:flex; flex-direction:column; gap: 8px;">
    <CfDropzone
      v-model="files"
      accept="image/*"
      :max-size="1024 * 1024"
      :max-files="3"
      hint="只接受图片,单文件最大 1 MB,最多 3 个"
      @reject="onReject"
    />
    <ul
      v-if="errors.length"
      style="margin: 0; padding-left: 16px; font-size: 12px; color: var(--status-error);"
    >
      <li v-for="(e, i) in errors" :key="i">{{ e }}</li>
    </ul>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfDropzone } from '@chufix-design/vue';

const files = ref<File[]>([]);
const reasons= {
  'too-large': '超出 1 MB',
  'too-many': '最多 3 个',
  'wrong-type': '只接受图片',
  duplicate: '重复',
};
const errors = ref<string[]>([]);

function onReject(items) {
  errors.value = items.map((it) => `${it.file.name} · ${reasons[it.reason]}`);
  setTimeout(() => (errors.value = []), 4000);
}
</script>

<template>
  <div style="display:flex; flex-direction:column; gap: 8px;">
    <CfDropzone
      v-model="files"
      accept="image/*"
      :max-size="1024 * 1024"
      :max-files="3"
      hint="只接受图片,单文件最大 1 MB,最多 3 个"
      @reject="onReject"
    />
    <ul
      v-if="errors.length"
      style="margin: 0; padding-left: 16px; font-size: 12px; color: var(--status-error);"
    >
      <li v-for="(e, i) in errors" :key="i">{{ e }}</li>
    </ul>
  </div>
</template>
import { useState } from 'react';
import { CfDropzone, type DropzoneRejection } from '@chufix-design/react';

export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
return (
  <CfDropzone
    value={files}
    accept="image/*"
    maxSize={1024 * 1024}
    maxFiles={3}
    hint="只接受图片,单文件最大 1 MB,最多 3 个"
    onChange={setFiles}
    onReject={(items: DropzoneRejection[]) => console.warn(items)}
  />
);
}
import { useState } from 'react';
import { CfDropzone } from '@chufix-design/react';

export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
return (
  <CfDropzone
    value={files}
    accept="image/*"
    maxSize={1024 * 1024}
    maxFiles={3}
    hint="只接受图片,单文件最大 1 MB,最多 3 个"
    onChange={setFiles}
    onReject={(items) => console.warn(items)}
  />
);
}

上传进度

把外部上传状态用 statuses(Vue / React)传进来,长度需与 modelValue / value 一致。status 取值 'pending' | 'uploading' | 'success' | 'error'progress 0 ~ 100;error 仅在 status === 'error' 时显示。

下面这个 demo 用 setTimeout 假装上传,实际项目里把 statuses 接到 fetch / XHR 的 progress 事件即可。

背景
点击或拖拽文件到此区域上传
选择文件后会模拟上传进度
<script setup lang="ts">
import { ref, watch } from 'vue';
import { CfDropzone, type DropzoneFileStatus } from '@chufix-design/vue';

const files = ref<File[]>([]);
const statuses = ref<DropzoneFileStatus[]>([]);

watch(files, (next) => {
  statuses.value = next.map((_, i) => statuses.value[i] ?? { progress: 0, status: 'pending' });
  next.forEach((_, i) => simulate(i));
});

function simulate(i: number) {
  if (statuses.value[i].status === 'success') return;
  statuses.value[i] = { progress: 0, status: 'uploading' };
  const tick = () => {
    const cur = statuses.value[i];
    if (!cur || cur.status !== 'uploading') return;
    const next = (cur.progress ?? 0) + 8 + Math.random() * 12;
    if (next >= 100) {
      statuses.value[i] = { progress: 100, status: 'success' };
    } else {
      statuses.value[i] = { progress: next, status: 'uploading' };
      setTimeout(tick, 280);
    }
  };
  setTimeout(tick, 200);
}
</script>

<template>
  <CfDropzone v-model="files" :statuses="statuses" hint="选择文件后会模拟上传进度" />
</template>
<script setup>
import { ref, watch } from 'vue';
import { CfDropzone } from '@chufix-design/vue';

const files = ref<File[]>([]);
const statuses = ref<DropzoneFileStatus[]>([]);

watch(files, (next) => {
  statuses.value = next.map((_, i) => statuses.value[i] ?? { progress: 0, status: 'pending' });
  next.forEach((_, i) => simulate(i));
});

function simulate(i) {
  if (statuses.value[i].status === 'success') return;
  statuses.value[i] = { progress: 0, status: 'uploading' };
  const tick = () => {
    const cur = statuses.value[i];
    if (!cur || cur.status !== 'uploading') return;
    const next = (cur.progress ?? 0) + 8 + Math.random() * 12;
    if (next >= 100) {
      statuses.value[i] = { progress: 100, status: 'success' };
    } else {
      statuses.value[i] = { progress: next, status: 'uploading' };
      setTimeout(tick, 280);
    }
  };
  setTimeout(tick, 200);
}
</script>

<template>
  <CfDropzone v-model="files" :statuses="statuses" hint="选择文件后会模拟上传进度" />
</template>
import { useState, useEffect } from 'react';
import { CfDropzone, type DropzoneFileStatus } from '@chufix-design/react';

export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
const [statuses, setStatuses] = useState<DropzoneFileStatus[]>([]);
useEffect(() => {
  setStatuses((prev) =>
    files.map((_, i) => prev[i] ?? { progress: 0, status: 'pending' })
  );
}, [files]);
return <CfDropzone value={files} statuses={statuses} onChange={setFiles} />;
}
import { useState, useEffect } from 'react';
import { CfDropzone } from '@chufix-design/react';

export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
const [statuses, setStatuses] = useState<DropzoneFileStatus[]>([]);
useEffect(() => {
  setStatuses((prev) =>
    files.map((_, i) => prev[i] ?? { progress: 0, status: 'pending' })
  );
}, [files]);
return <CfDropzone value={files} statuses={statuses} onChange={setFiles} />;
}

API

属性类型默认值说明
modelValue (Vue) / value (React)File[][]当前文件数组
acceptstring<input>accept,逗号分隔;支持 image/*.ext 形式
multiplebooleantrue单选 / 多选;false 时新选会替换旧值
maxSizenumber单文件最大字节数
maxFilesnumber总数量上限
disabledbooleanfalse禁用
size'sm' | 'md' | 'lg''md'尺寸
hintstring副标题文字
statusesDropzoneFileStatus[]与文件平行的状态数组:{ progress, status, error }
hideListbooleanfalse不渲染内置文件列表,由你自定义

事件:update:modelValue / change / reject / remove(React 端:onChange / onReject / onRemove)。

插槽:默认 slot 替换标题区域;#icon 替换默认上传插图(默认来自 CfStatusIllustration variant="upload")。

反馈与讨论

Dropzone 文件拖拽上传 的讨论

0
0 / 600
一键发送
正在加载评论...