Dropzone
File picker that accepts both drag and click, with built-in file list, progress bar, and reject callback.
Basic usage
v-model binds a File[]. Drag files into the dashed border, or click the area to open the file dialog. Selected files appear in a compact list with size and a remove button per row.
<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>
import { useState } from 'react';
import { CfDropzone } from '@chufix-design/react';
export default function Demo() {
const [files, setFiles] = useState<File[]>([]);
return (
<CfDropzone
value={files}
hint="Drop files here or click to select; multi-file supported"
onChange={setFiles}
/>
);
} Constraints and reject callback
accept / maxSize / maxFiles define what’s accepted. Any rejected file is reported through the reject (Vue) / onReject (React) callback with a specific reason ('too-large' | 'too-many' | 'wrong-type' | 'duplicate').
<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>
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="Images only, up to 1 MB each, max 3 files"
onChange={setFiles}
onReject={(items: DropzoneRejection[]) => console.warn(items)}
/>
);
} Upload progress
Pass external upload state via statuses (Vue / React); its length must match modelValue / value. status is 'pending' | 'uploading' | 'success' | 'error'; progress is 0–100; error is shown only when status === 'error'.
The demo below fakes uploads with setTimeout. In a real project, wire statuses to the progress events of fetch / XHR.
<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>
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} />;
} API
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue (Vue) / value (React) | File[] | [] | Current files |
accept | string | — | Same as <input>’s accept, comma-separated; supports image/* and .ext |
multiple | boolean | true | Single / multi-select; false makes a new selection replace the previous value |
maxSize | number | — | Max bytes per file |
maxFiles | number | — | Max total count |
disabled | boolean | false | Disabled |
size | 'sm' | 'md' | 'lg' | 'md' | Size |
hint | string | — | Subtitle text |
statuses | DropzoneFileStatus[] | — | Per-file status array: { progress, status, error } |
hideList | boolean | false | Skip the built-in file list and render your own |
Events: update:modelValue / change / reject / remove (React: onChange / onReject / onRemove).
Slots: the default slot replaces the title area; #icon replaces the default upload illustration (default comes from CfStatusIllustration variant="upload").
反馈与讨论
Dropzone · Discussion