Attachments File Upload Component ๐ช๏ธ โ
Introduction โ
The Attachments
component is a feature-rich attachment management component that supports file list display, upload, drag-and-drop interaction, scroll browsing, and more. It is suitable for scenarios that require handling multiple file uploads and displays (such as form attachments, file management interfaces). The component has a built-in file upload button, drag-and-drop prompt area, and provides flexible custom slots and style configuration.
Code Examples โ
Basic Usage โ
Basic file list display and upload functionality, supports automatic file card generation.
<script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import { ref } from 'vue';
type SelfFilesCardProps = FilesCardProps & {
id?: number;
};
const files = ref<SelfFilesCardProps[]>([]);
function handleBeforUpload(file: any) {
console.log('before', file);
if (file.size > 1024 * 1024 * 2) {
ElMessage.error('File size cannot exceed 2MB!');
return false;
}
}
async function handleUploadDrop(files: any, props: any) {
console.log('drop', files);
console.log('props', props);
if (files && files.length > 0) {
if (files[0].type === '') {
ElMessage.error('Folder upload is not allowed!');
return false;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
await handleHttpRequest({ file });
}
}
}
async function handleHttpRequest(options: any) {
const formData = new FormData();
formData.append('file', options.file);
ElMessage.info('Uploading...');
setTimeout(() => {
const res = {
message: 'File upload successful',
fileName: options.file.name,
uid: options.file.uid,
fileSize: options.file.size,
imgFile: options.file
};
files.value.push({
id: files.value.length,
uid: res.uid,
name: res.fileName,
fileSize: res.fileSize,
imgFile: res.imgFile,
showDelIcon: true,
imgVariant: 'square'
});
ElMessage.success('Upload successful');
}, 1000);
}
function handleDeleteCard(item: SelfFilesCardProps) {
files.value = files.value.filter((items: any) => items.id !== item.id);
console.log('delete', item);
ElMessage.success('Delete successful');
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
/>
</div>
</template>
<style scoped lang="less"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Scroll Mode โ
Supports three layout modes: horizontal scroll (scrollX
), vertical scroll (scrollY
), and auto wrap (wrap
). Default is horizontal scroll.
<script setup lang="ts">
import type {
FilesCardProps,
FilesType
} from 'vue-element-plus-x/types/FilesCard';
import { ref } from 'vue';
type SelfFilesCardProps = FilesCardProps & {
id?: number;
};
const colorMap: Record<FilesType, string> = {
word: '#5E74A8',
excel: '#4A6B4A',
ppt: '#C27C40',
pdf: '#5A6976',
txt: '#D4C58C',
mark: '#FFA500',
image: '#8E7CC3',
audio: '#A67B5B',
video: '#4A5568',
three: '#5F9E86',
code: '#4B636E',
database: '#4A5A6B',
link: '#5D7CBA',
zip: '#8B5E3C',
file: '#AAB2BF',
unknown: '#888888'
};
const files = ref<SelfFilesCardProps[]>([]);
const typeList = Object.keys(colorMap);
onMounted(() => {
for (let index = 0; index < 30; index++) {
files.value.push({
id: index,
uid: index,
name: `File ${index}`,
fileSize: 1024 * 2,
fileType: typeList[
Math.floor(Math.random() * typeList.length)
] as FilesType,
// description: `Description ${index}`,
url: 'https://www.baidu.com',
thumbUrl: 'https://www.baidu.com',
imgFile: new File([], 'test.txt'),
showDelIcon: true
});
}
});
function handleBeforUpload(file: any) {
console.log('before', file);
if (file.size > 1024 * 1024 * 2) {
ElMessage.error('File size cannot exceed 2MB!');
return false;
}
}
async function handleUploadDrop(files: any, props: any) {
console.log('drop', files);
console.log('props', props);
if (files && files.length > 0) {
if (files[0].type === '') {
ElMessage.error('Folder upload is not allowed!');
return false;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
await handleHttpRequest({ file });
}
}
}
async function handleHttpRequest(options: any) {
const formData = new FormData();
formData.append('file', options.file);
ElMessage.info('Uploading...');
setTimeout(() => {
const res = {
message: 'File upload successful',
fileName: options.file.name,
uid: options.file.uid,
fileSize: options.file.size,
imgFile: options.file
};
files.value.push({
id: files.value.length,
uid: res.uid,
name: res.fileName,
fileSize: res.fileSize,
imgFile: res.imgFile,
showDelIcon: true,
imgVariant: 'square'
});
ElMessage.success('Upload successful');
}, 1000);
}
function handleDeleteCard(item: SelfFilesCardProps) {
files.value = files.value.filter((items: any) => items.id !== item.id);
console.log('delete', item);
ElMessage.success('Delete successful');
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div>scrollX</div>
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
overflow="scrollX"
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
/>
<div>scrollY</div>
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
overflow="scrollY"
:list-style="{ height: '200px' }"
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
/>
<div>wrap</div>
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
overflow="wrap"
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
/>
</div>
</template>
<style scoped lang="less"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
Custom File List โ
Customize file list display content through slots (overrides default FilesCard
component).
<script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import { ref } from 'vue';
type SelfFilesCardProps = FilesCardProps & {
id?: number;
};
const files = ref<SelfFilesCardProps[]>([]);
function handleBeforUpload(file: any) {
console.log('before', file);
if (file.size > 1024 * 1024 * 2) {
ElMessage.error('File size cannot exceed 2MB!');
return false;
}
}
async function handleUploadDrop(files: any, props: any) {
console.log('drop', files);
console.log('props', props);
if (files && files.length > 0) {
if (files[0].type === '') {
ElMessage.error('Folder upload is not allowed!');
return false;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
await handleHttpRequest({ file });
}
}
}
async function handleHttpRequest(options: any) {
const formData = new FormData();
formData.append('file', options.file);
ElMessage.info('Uploading...');
setTimeout(() => {
const res = {
message: 'File upload successful',
fileName: options.file.name,
uid: options.file.uid,
fileSize: options.file.size,
imgFile: options.file
};
files.value.push({
id: files.value.length,
uid: res.uid,
name: res.fileName,
fileSize: res.fileSize,
imgFile: res.imgFile,
showDelIcon: true,
imgVariant: 'square'
});
ElMessage.success('Upload successful');
}, 1000);
}
function handleDeleteCard(item: SelfFilesCardProps) {
files.value = files.value.filter((items: any) => items.id !== item.id);
console.log('delete', item);
ElMessage.success('Delete successful');
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
>
<template #file-list="{ items }">
<div class="custom-list">
<div v-for="(item, index) in items" :key="index" class="custom-item">
<div class="custom-item-name">
{{ item.name }}
</div>
</div>
</div>
</template>
</Attachments>
</div>
</template>
<style scoped lang="less">
.custom-list {
display: flex;
gap: 12px;
}
.custom-item {
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
Drag and Drop Upload โ
Set fullscreen drag upload:
The drag
property enables drag and drop upload functionality, supporting custom drag target areas and visual feedback.
The dragTarget
property can be an id selector string, a Ref instance, or an HTMLElement DOM. If not set, the default drag area is the current list.
If you want to enable drag upload for the entire page, set drag
to true
and set drag-target
to 'document.body'
.
<script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
type SelfFilesCardProps = FilesCardProps & {
id?: number;
};
const files = ref<SelfFilesCardProps[]>([]);
const isFull = ref(false);
const dragArea = ref();
watch(
() => isFull.value,
() => {
console.log('isFull.value', isFull.value);
if (isFull.value) {
dragArea.value = document.body;
}
else {
dragArea.value = 'drag-area';
}
},
{ immediate: true, deep: true }
);
function handleBeforUpload(file: any) {
console.log('before', file);
if (file.size > 1024 * 1024 * 2) {
ElMessage.error('File size cannot exceed 2MB!');
return false;
}
}
async function handleUploadDrop(files: any, props: any) {
console.log('drop', files);
console.log('props', props);
if (files && files.length > 0) {
if (files[0].type === '') {
ElMessage.error('Folder upload is not allowed!');
return false;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
await handleHttpRequest({ file });
}
}
}
async function handleHttpRequest(options: any) {
const formData = new FormData();
formData.append('file', options.file);
ElMessage.info('Uploading...');
setTimeout(() => {
const res = {
message: 'File upload successful',
fileName: options.file.name,
uid: options.file.uid,
fileSize: options.file.size,
imgFile: options.file
};
files.value.push({
id: files.value.length,
uid: res.uid,
name: res.fileName,
fileSize: res.fileSize,
imgFile: res.imgFile,
showDelIcon: true,
imgVariant: 'square'
});
ElMessage.success('Upload successful');
}, 1000);
}
function handleDeleteCard(item: SelfFilesCardProps) {
files.value = files.value.filter((items: any) => items.id !== item.id);
console.log('delete', item);
ElMessage.success('Delete successful');
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<p>Set fullscreen drag upload: <el-switch v-model="isFull" /></p>
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
:drag-target="dragArea"
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
/>
<div
id="drag-area"
style="
border: 2px dashed #ccc;
padding: 20px;
height: 250px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
"
>
Drag files here to upload
</div>
</div>
</template>
<style scoped lang="less"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
Custom Scroll Buttons โ
Override default left and right scroll button styles and interactions.
<script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import { ref } from 'vue';
type SelfFilesCardProps = FilesCardProps & {
id?: number;
};
const files = ref<SelfFilesCardProps[]>([]);
function handleBeforUpload(file: any) {
console.log('before', file);
if (file.size > 1024 * 1024 * 2) {
ElMessage.error('File size cannot exceed 2MB!');
return false;
}
}
async function handleUploadDrop(files: any, props: any) {
console.log('drop', files);
console.log('props', props);
if (files && files.length > 0) {
if (files[0].type === '') {
ElMessage.error('Folder upload is not allowed!');
return false;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
await handleHttpRequest({ file });
}
}
}
async function handleHttpRequest(options: any) {
const formData = new FormData();
formData.append('file', options.file);
ElMessage.info('Uploading...');
setTimeout(() => {
const res = {
message: 'File upload successful',
fileName: options.file.name,
uid: options.file.uid,
fileSize: options.file.size,
imgFile: options.file
};
files.value.push({
id: files.value.length,
uid: res.uid,
name: res.fileName,
fileSize: res.fileSize,
imgFile: res.imgFile,
showDelIcon: true,
imgVariant: 'square'
});
ElMessage.success('Upload successful');
}, 1000);
}
function handleDeleteCard(item: SelfFilesCardProps) {
files.value = files.value.filter((items: any) => items.id !== item.id);
console.log('delete', item);
ElMessage.success('Delete successful');
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Attachments
:file-list="files"
:http-request="handleHttpRequest"
:items="files"
drag
overflow="scrollX"
:before-upload="handleBeforUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
>
<template #prev-button="{ show, onScrollLeft }">
<button v-if="show" class="custom-prev" @click="onScrollLeft">
๐
</button>
</template>
<template #next-button="{ show, onScrollRight }">
<button v-if="show" class="custom-next" @click="onScrollRight">
๐
</button>
</template>
</Attachments>
</div>
</template>
<style scoped lang="less">
.custom-prev,
.custom-next {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border: 2px solid rgba(255, 255, 255, 0.5);
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
transition: all 0.3s ease;
}
.custom-prev {
left: 8px;
}
.custom-next {
right: 8px;
}
.custom-prev:hover,
.custom-next:hover {
background-color: rgba(0, 0, 0, 0.8);
color: white;
border-color: rgba(255, 255, 255, 0.8);
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
Props โ
Name | Type | Required | Default | Description |
---|---|---|---|---|
items | FilesCardProps[] | No | [] | File list data (includes basic file info such as name, type, status, etc.) |
overflow | 'scrollX' | 'scrollY' | 'wrap' | No | 'scrollX' | Scroll layout mode (horizontal scroll/vertical scroll/wrap) |
listStyle | CSSProperties | No | {} | Custom style for the list container |
uploadIconSize | string | No | '64px' | Upload button icon size |
dragTarget | string | Ref<HTMLElement> | null | No | null | Drag target element (supports selector string or DOM ref, defaults to the component itself) |
hideUpload | boolean | No | false | Whether to hide the default upload button |
limit | number | No | undefined | File quantity limit (hides upload button if exceeded) |
beforeUpload | (file: File) => boolean | No | undefined | Pre-upload validation function (return false to prevent upload) |
httpRequest | (options: { file: File }) => Promise<void> | No | undefined | Custom upload request function (must return a Promise) |
Slots โ
Slot Name | Slot Parameter | Description |
---|---|---|
#file-list | { items: FilesCardProps[] } | Custom file list content (overrides default FilesCard display) |
#prev-button | { show: boolean, onScrollLeft: () => void } | Custom left scroll button (scrollX mode), show controls visibility |
#next-button | { show: boolean, onScrollRight: () => void } | Custom right scroll button (scrollX mode), show controls visibility |
#empty-upload | - | Custom upload area when file list is empty (default shows plus upload button) |
#no-empty-upload | - | Custom upload placeholder when file list is not empty (default shows plus upload button) |
#drop-area | - | Custom overlay content during drag upload (default shows upload icon and text) |
Events โ
Event Name | Callback Parameter | Description |
---|---|---|
uploadChange | (file: File, fileList: FileListProps) | Triggered when file selection changes (includes selected file and current file list) |
uploadSuccess | (response: any, file: File, fileList: FileListProps) | Triggered when file upload succeeds (returns response, current file, and file list) |
uploadError | (error: any, file: File, fileList: FileListProps) | Triggered when file upload fails (returns error, current file, and file list) |
uploadDrop | (files: File[], props: FileListProps) | Triggered when files are dropped (includes dropped files array and component props) |
deleteCard | (item: FilesCardProps, index: number) | Triggered when the file card delete button is clicked (returns deleted file info and index) |
Support for el-upload Props โ
The component internally uses the elementplus el-upload
component, so it supports most of its upload properties, such as: httpRequest
, beforeUpload
, etc. For details, please refer to: element-plus/upload
Features โ
- Multiple Layout Modes: Supports
scrollX
(horizontal scroll),scrollY
(vertical scroll), andwrap
(auto wrap) layouts to adapt to different screen spaces and file quantities. - Drag-and-Drop Upload Interaction: Built-in drag target area (customizable via
dragTarget
), shows a semi-transparent overlay prompt during drag, supports folder filtering and file type validation. - Highly Customizable: Fully customize file list display via the
#file-list
slot (e.g., replace with custom card component), supports custom scroll buttons and upload button styles. - File Status Management: With the
FilesCard
component, supports visual states for uploading (progress bar), completed, failed, etc., and automatically syncs file list updates.