CYM
文件上传

文件上传

文件上传

  • 单一文件上传[form-data]
  • 单一文件上传[BASE64],只适合图片
  • 单一文件上传[缩略图处理]
  • 单一文件上传[进度管控]
  • 多文件上传
  • 拖拽上传
  • 大文件上传
    以下代码均为示例代码,请根据实际情况进行修改,并没有请求后台,请自行添加

单一文件上传[form-data]

单一文件上传[form-data]

文件上传

选择文件后将在这里显示文件信息

1
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
// 获取DOM元素
const fdFileInput = document.getElementById('fd-fileInput');
const fdPreviewArea = document.getElementById('fd-previewArea');
// 为文件输入框添加change事件监听器
// 当用户选择文件时,此函数会被触发
fdFileInput.addEventListener('change', function(e) {
// 获取用户选择的第一个文件
// e.target.files 是一个 FileList 对象,包含所有选择的文件
const file = e.target.files[0];
// 如果有选择文件,则显示文件信息
if (file) {
// 使用模板字符串更新预览区域的HTML内容
// 显示文件名、大小(转换为KB)和文件类型
fdPreviewArea.innerHTML = `
<p><strong>文件名:</strong>${file.name}</p>
<p><strong>文件大小:</strong>${(file.size / 1024).toFixed(2)} KB</p>
<p><strong>文件类型:</strong>${file.type || '未知'}</p>
`;
}
});
// 文件上传函数
// 当点击上传按钮时触发
function uploadFormDataFile() {
// 获取选择的文件
const file = fdFileInput.files[0];
// 检查是否选择了文件
if (!file) {
alert('请先选择文件!');
return;
}
// 创建FormData对象,用于将文件数据发送到服务器
const formData = new FormData();
// 将文件添加到FormData中,'file'是上传字段的名称
formData.append('file', file);
// 使用fetch API发送POST请求上传文件
// '/upload'是服务器端的上传接口地址
fetch('/upload', {
method: 'POST', // 使用POST方法
body: formData // 请求体包含文件数据
})
// 将响应转换为JSON格式
.then(response => response.json())
// 上传成功的处理
.then(data => {
alert('文件上传成功!');
})
// 上传失败的错误处理
.catch(error => {
console.error('上传失败:', error);
alert('文件上传失败,请重试!');
});
}

单一文件上传[BASE64],只适合图片

单一文件上传[BASE64],只适合图片

图片上传 (Base64)

选择图片后将在这里预览

预览图片
1
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
// 获取DOM元素
const b64FileInput = document.getElementById('fileInput');
const b64ImagePreview = document.getElementById('imagePreview');
// 为文件输入框添加change事件监听器
b64FileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
if (!file.type.startsWith('image/')) {
alert('请选择图片文件!');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
b64ImagePreview.src = e.target.result;
b64ImagePreview.style.display = 'block';
};
reader.readAsDataURL(file);
}
});
function uploadBase64Image() {
const file = b64FileInput.files[0];
if (!file) {
alert('请先选择图片!');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const base64Data = e.target.result;
// 这里替换为实际的上传接口
fetch('/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
image: base64Data,
filename: file.name
})
})
.then(response => response.json())
.then(data => {
alert('图片上传成功!');
})
.catch(error => {
console.error('上传失败:', error);
alert('图片上传失败,请重试!');
});
};
reader.readAsDataURL(file);
}

单一文件上传[缩略图处理]

单一文件上传[缩略图处理]

图片上传与缩略图处理

原图预览
原图预览
缩略图预览
缩略图预览
×
1
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
// 获取DOM元素
const thumbFileInput = document.getElementById('fileInput');
const thumbOriginalPreview = document.getElementById('originalPreview');
const thumbThumbnailPreview = document.getElementById('thumbnailPreview');
const thumbWidth = document.getElementById('thumbWidth');
const thumbHeight = document.getElementById('thumbHeight');
// 文件选择事件处理
thumbFileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
// 检查是否为图片文件
if (!file.type.startsWith('image/')) {
alert('请选择图片文件!');
return;
}
// 创建文件读取器
const reader = new FileReader();
reader.onload = function(e) {
// 显示原图预览
thumbOriginalPreview.src = e.target.result;
// 自动生成缩略图
generateThumbnail();
};
reader.readAsDataURL(file);
}
});
// 生成缩略图
function generateThumbnail() {
// 创建Canvas元素
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置Canvas尺寸
canvas.width = parseInt(thumbWidth.value);
canvas.height = parseInt(thumbHeight.value);
// 在Canvas上绘制缩放后的图片
ctx.drawImage(thumbOriginalPreview, 0, 0, canvas.width, canvas.height);
// 将Canvas内容转换为图片URL并显示
thumbThumbnailPreview.src = canvas.toDataURL('image/jpeg', 0.8);
}
// 监听输入框变化,自动更新缩略图
thumbWidth.addEventListener('change', generateThumbnail);
thumbHeight.addEventListener('change', generateThumbnail);

单一文件上传[进度管控]

单一文件上传[进度管控]

文件上传(带进度条)

0%

选择文件后将在这里显示文件信息

1
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
// 为文件输入框添加change事件监听器
// 当用户选择文件时,此函数会被触发
document.getElementById('fileInput').addEventListener('change', function(e) {
// 获取用户选择的第一个文件
const file = e.target.files[0];
// 如果有选择文件,则显示文件信息
if (file) {
// 获取文件信息显示区域的DOM元素
const fileInfo = document.getElementById('fileInfo');
// 使用模板字符串更新文件信息区域的HTML内容
// 显示文件名、大小(转换为KB)和文件类型
fileInfo.innerHTML = `
<p><strong>文件名:</strong>${file.name}</p>
<p><strong>文件大小:</strong>${(file.size / 1024).toFixed(2)} KB</p>
<p><strong>文件类型:</strong>${file.type || '未知'}</p>
`;
}
});

// 文件上传函数
// 当点击上传按钮时触发
function uploadFile() {
// 获取文件输入框元素
const fileInput = document.getElementById('fileInput');
// 获取选择的文件
const file = fileInput.files[0];

// 检查是否选择了文件
if (!file) {
alert('请先选择文件!');
return;
}

// 获取进度条相关的DOM元素
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');

// 显示进度条容器
progressContainer.style.display = 'block';

// 创建XMLHttpRequest对象用于发送请求
const xhr = new XMLHttpRequest();
// 创建FormData对象,用于将文件数据发送到服务器
const formData = new FormData();
// 将文件添加到FormData中
formData.append('file', file);

// 监听上传进度事件
xhr.upload.onprogress = function(e) {
// 检查进度信息是否可计算
if (e.lengthComputable) {
// 计算上传进度百分比
const percentComplete = (e.loaded / e.total) * 100;
// 更新进度条宽度
progressBar.style.width = percentComplete + '%';
// 更新进度文本
progressText.textContent = percentComplete.toFixed(1) + '%';
}
};

// 监听请求完成事件
xhr.onload = function() {
// 检查请求是否成功(状态码200)
if (xhr.status === 200) {
alert('文件上传成功!');
} else {
alert('文件上传失败,请重试!');
}
};

// 监听请求错误事件
xhr.onerror = function() {
alert('上传出错,请重试!');
};

// 配置并发送请求
// 使用POST方法发送请求到'/upload'接口
xhr.open('POST', '/upload', true);
// 发送包含文件数据的FormData对象
xhr.send(formData);
}

多文件上传

多文件上传

多文件上传

请选择要上传的文件
1
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
// 用于存储所有选择的文件的数组
let selectedFiles = [];

// 为文件输入框添加change事件监听器
// 当用户选择文件时,此函数会被触发
document.getElementById('fileInput').addEventListener('change', function(e) {
// 将FileList对象转换为数组
const files = Array.from(e.target.files);
// 将新选择的文件添加到已选择文件数组中
selectedFiles = selectedFiles.concat(files);
// 更新文件列表显示
updateFilesList();
});

// 更新文件列表显示函数
function updateFilesList() {
// 获取文件列表容器元素
const filesList = document.getElementById('filesList');

// 如果没有选择文件,显示提示信息
if (selectedFiles.length === 0) {
filesList.innerHTML = '<div class="empty-message">请选择要上传的文件</div>';
return;
}

// 使用map方法生成文件列表HTML
// 每个文件项包含文件名、大小和删除按钮
filesList.innerHTML = selectedFiles.map((file, index) => `
<div class="file-item">
<div class="file-info">
<p class="file-name">${file.name}</p>
<p class="file-size">${(file.size / 1024).toFixed(2)} KB</p>
</div>
<button class="remove-btn" onclick="removeFile(${index})">移除</button>
</div>
`).join('');
}

// 移除指定索引的文件
function removeFile(index) {
// 从数组中删除指定索引的文件
selectedFiles.splice(index, 1);
// 更新文件列表显示
updateFilesList();
}

// 上传所有选择的文件
function uploadFiles() {
// 检查是否有选择文件
if (selectedFiles.length === 0) {
alert('请先选择文件!');
return;
}

// 创建FormData对象,用于将文件数据发送到服务器
const formData = new FormData();
// 遍历所有选择的文件,将它们添加到FormData中
selectedFiles.forEach((file, index) => {
formData.append(`file${index}`, file);
});

// 使用fetch API发送POST请求上传文件
// '/upload-multiple'是服务器端的多文件上传接口
fetch('/upload-multiple', {
method: 'POST', // 使用POST方法
body: formData // 请求体包含所有文件数据
})
// 将响应转换为JSON格式
.then(response => response.json())
// 上传成功的处理
.then(data => {
alert('所有文件上传成功!');
// 清空已选择的文件列表
selectedFiles = [];
// 更新文件列表显示
updateFilesList();
})
// 上传失败的错误处理
.catch(error => {
console.error('上传失败:', error);
alert('文件上传失败,请重试!');
});
}

拖拽上传

拖拽上传
拖拽文件到这里上传
或点击选择文件
1
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
// 获取DOM元素
const dropZone = document.getElementById('dropZone');
const uploadList = document.getElementById('uploadList');

// 阻止默认拖放行为
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});

function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}

// 处理拖放视觉反馈
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});

['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});

function highlight(e) {
dropZone.classList.add('dragover');
}

function unhighlight(e) {
dropZone.classList.remove('dragover');
}

// 处理文件拖放
dropZone.addEventListener('drop', handleDrop, false);
dropZone.addEventListener('click', () => {
// 点击上传区域触发文件选择
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.onchange = (e) => {
handleFiles(Array.from(e.target.files));
};
input.click();
});

function handleDrop(e) {
const files = Array.from(e.dataTransfer.files);
handleFiles(files);
}

function handleFiles(files) {
files.forEach(uploadFile);
}

function uploadFile(file) {
// 创建上传项UI
const uploadItem = document.createElement('div');
uploadItem.className = 'upload-item';
uploadItem.innerHTML = `
<div class="upload-item-name">${file.name}</div>
<div class="upload-item-status">准备上传...</div>
`;
uploadList.appendChild(uploadItem);

// 这里是上传逻辑的注释说明
// 1. 创建FormData对象
// 2. 使用fetch API发送请求
// 3. 处理上传结果
// const formData = new FormData();
// formData.append('file', file);
// fetch('/upload', {
// method: 'POST',
// body: formData
// })
// .then(response => response.json())
// .then(data => {
// uploadItem.querySelector('.upload-item-status').textContent = '上传成功';
// })
// .catch(error => {
// uploadItem.querySelector('.upload-item-status').textContent = '上传失败';
// });

// 模拟上传成功
setTimeout(() => {
uploadItem.querySelector('.upload-item-status').textContent = '上传成功';
}, 1500);
}

大文件上传

大文件上传(支持断点续传)

大文件上传(支持断点续传)

1
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// 定义分片大小为1MB
const CHUNK_SIZE = 1024 * 1024; // 1MB per chunk
// 当前正在上传的文件
let currentFile = null;

// 获取DOM元素
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');

// 为文件输入框添加change事件监听器
fileInput.addEventListener('change', handleFileSelect);

// 生成文件的唯一标识符
// 使用文件名、大小和最后修改时间组合生成
function generateFileId(file) {
return `${file.name}-${file.size}-${file.lastModified}`;
}

// 从本地存储获取上传进度
// 用于实现断点续传功能
function getUploadProgress(fileId) {
// 从localStorage中获取保存的进度信息
const progress = localStorage.getItem(`upload-progress-${fileId}`);
// 如果有进度信息则解析并返回,否则返回null
return progress ? JSON.parse(progress) : null;
}

// 保存上传进度到本地存储
// 记录已上传的分片数量和总分片数
function saveUploadProgress(fileId, uploadedChunks, totalChunks) {
localStorage.setItem(`upload-progress-${fileId}`, JSON.stringify({
uploadedChunks, // 已上传的分片数
totalChunks, // 总分片数
timestamp: Date.now() // 保存时的时间戳
}));
}

// 清除上传进度
// 当文件上传完成或放弃上传时调用
function clearUploadProgress(fileId) {
localStorage.removeItem(`upload-progress-${fileId}`);
}

// 处理文件选择事件
function handleFileSelect(e) {
// 获取选择的第一个文件
const file = e.target.files[0];
if (file) handleFile(file);
}

// 处理选择的文件
// 创建文件显示元素并检查是否存在未完成的上传
function handleFile(file) {
// 保存当前文件
currentFile = file;
// 生成文件唯一标识符
const fileId = generateFileId(file);

// 创建文件项元素
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.id = fileId;
// 设置文件信息显示
fileItem.innerHTML = `
<div class="file-info">
<p class="file-name">${file.name}</p>
<p class="file-size">${(file.size / (1024 * 1024)).toFixed(2)} MB</p>
</div>
<div class="file-status status-pending" id="status-${fileId}">准备上传...</div>
`;
fileList.appendChild(fileItem);

// 检查是否存在未完成的上传进度
const progress = getUploadProgress(fileId);
if (progress && progress.uploadedChunks < progress.totalChunks) {
// 如果存在未完成的上传,显示续传选项
const statusElement = document.getElementById(`status-${fileId}`);
statusElement.textContent = `发现未完成的上传(${Math.round(progress.uploadedChunks / progress.totalChunks * 100)}%)`;

// 创建续传按钮
const resumeButton = document.createElement('button');
resumeButton.className = 'resume-button';
resumeButton.textContent = '继续上传';
resumeButton.onclick = () => uploadChunks(file, fileId, progress.uploadedChunks);
fileItem.appendChild(resumeButton);
} else {
// 如果是新文件,直接开始上传
uploadChunks(file, fileId, 0);
}
}

// 分片上传函数
// 将文件分成多个小块逐个上传
async function uploadChunks(file, fileId, startChunk = 0) {
// 计算总分片数
const chunks = Math.ceil(file.size / CHUNK_SIZE);
// 获取状态显示元素
const statusElement = document.getElementById(`status-${fileId}`);
// 用Set记录已上传的分片
const uploadedChunks = new Set();

// 恢复之前已上传的分片记录
for (let i = 0; i < startChunk; i++) {
uploadedChunks.add(i);
}

// 从断点处开始上传每个分片
for (let i = startChunk; i < chunks; i++) {
// 计算当前分片的起始和结束位置
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
// 从文件中切割出当前分片
const chunk = file.slice(start, end);

// 更新上传状态显示
statusElement.className = 'file-status status-uploading';
statusElement.textContent = `上传中 ${Math.round((i + 1) / chunks * 100)}%`;

try {
// 创建FormData对象,添加分片相关信息
const formData = new FormData();
formData.append('chunk', chunk); // 分片数据
formData.append('chunkIndex', i); // 分片索引
formData.append('totalChunks', chunks); // 总分片数
formData.append('fileName', file.name); // 文件名
formData.append('fileId', fileId); // 文件ID

// 发送分片上传请求
await fetch('/upload-chunk', {
method: 'POST',
body: formData
});

// 记录已上传的分片
uploadedChunks.add(i);
// 保存上传进度
saveUploadProgress(fileId, uploadedChunks.size, chunks);

} catch (error) {
// 处理上传失败的情况
console.error('分片上传失败:', error);
statusElement.className = 'file-status status-error';
statusElement.textContent = '上传失败,点击继续上传';

// 创建重试按钮
const retryButton = document.createElement('button');
retryButton.className = 'resume-button';
retryButton.textContent = '重试';
retryButton.onclick = () => uploadChunks(file, fileId, i);
// 移除已存在的重试按钮(如果有)
const existingButton = statusElement.parentElement.querySelector('.resume-button');
if (existingButton) {
existingButton.remove();
}
statusElement.parentElement.appendChild(retryButton);
return;
}
}

// 所有分片上传完成后的处理
statusElement.className = 'file-status status-success';
statusElement.textContent = '上传完成';

// 清除保存的上传进度
clearUploadProgress(fileId);

// 通知服务器合并所有分片
try {
await fetch('/merge-chunks', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: file.name,
totalChunks: chunks,
fileId: fileId
})
});
} catch (error) {
// 处理合并失败的情况
console.error('分片合并失败:', error);
statusElement.className = 'file-status status-error';
statusElement.textContent = '文件合并失败';
}
}