该项目涉及到的一些关键知识点

选择多个文件
在 JavaScript 中,要实现选择多个文件,可以使用 HTML 的<input>元素的multiple属性。以下是示例代码:

<input type="file" multiple>

选择文件夹
在 JavaScript 中要实现选择文件夹的功能,由于浏览器的安全限制,直接选择文件夹可能具有一定的局限性。

  • webkitdirectory属性适用于部分现代浏览器
  • mozdirectory属性适配火狐浏览器
  • odirectory属性适配 Opera 内核浏览器

以下是示例代码:

<input type="file" webkitdirectory mozdirectory odirectory>

拖拽文件或文件夹
在 JavaScript 中实现拖拽文件或文件夹的交互,主要涉及以下几个关键步骤和知识点:ondrop 事件

定义和用法
当被拖动的元素或选取的文本被放置在目标区域时,会触发 ondrop 事件。它通常与拖放功能结合使用,用于在元素被放置到特定区域时执行相应的操作。

触发条件
在拖放操作中,当拖动的元素或文本被释放到目标区域时,就会触发 ondrop 事件。

相关事件
在拖放过程中,还会涉及其他相关事件,如:

  • ondragstart:在拖动开始时触发。
  • ondrag:在拖动过程中持续触发。
  • ondragend:在拖动结束时触发。
  • ondragenter:当拖动元素进入目标区域时触发。
  • ondragover:当拖动元素在目标区域上移动时触发,为了使 ondrop 事件能够正常触发,通常需要在 ondragover 事件处理程序中调用 event.preventDefault() 来阻止浏览器的默认行为。
  • ondragleave:当拖动元素离开目标区域时触发。

上传前

20240730upload.png

20240730upload.png

上传中
uo1.png

uo1.png

上传结束
su.png

su.png

部分重要代码如下

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <!--告诉IE浏览器以最高版本的标准模式来渲染页面--> <meta http-equiv="x-ua-compatible" content="IE=edge"> <!--width=device-width 表示将视口的宽度设置为设备的屏幕宽度--> <!--“initial-scale=1.0 初始的缩放比例为1.0--> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>图片上传demo</title> <link rel="stylesheet" href="./static/font_4633578_xmf7zv1kiyn/iconfont.css"> <style> .main { width: 800px; height: 300px; position: absolute; top: 20%; left: 50%; transform: translate(-50%, -50%); } .container { border: 2px dashed #808080; width: 100%; height: 150px; margin: 20px auto; display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ } #chooseFile,#chooseDir { display: none; } .my_click, .startUploads { width: 100px; height: 40px; font-size: 15px; line-height: 40px; background-color: #1E90FF; color: white; border: #1E90FF; outline: none; margin-bottom: 20px; } table { width: 100%; border-collapse: collapse; background-color: #F9F9F9; border: 1px solid #D3D3D3; opacity: 0.7; overflow: auto; max-height: 300px; margin-bottom: 20px; } .table-header { height: 50px; } th, td { border: 1px solid #D3D3D3; padding: 15px; text-align: left; font-size: 14px; } th { background-color: #EDEDED; color: black; font-size: 14px; } th:nth-child(1), td:nth-child(1) { width: 15%; } th:nth-child(2), td:nth-child(2) { width: 10%; } th:nth-child(3), td:nth-child(3) { width: 13%; } th:nth-child(5), td:nth-child(5) { width: 10%; } th:nth-child(4), td:nth-child(4) { width: 50%; } .progress { background-color: #f0f0f0; width: 100%; height: 20px; border-radius: 5px; overflow: hidden; position: relative; } .progress-bar { height: 100%; background-color: #4CAF50; } .progress-percentage { position: absolute; top: 0; right: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: white; } .delete { border: none; background-color: #F9F9F9; } .icon-shangchuan { font-size: 30px; display: inline-block; vertical-align: middle; } .text { font-size: 14px; } .icon-no-data { font-size: 50px; opacity: 0.4; } .no-data { position: relative; } .no-data i { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0; } </style> </head> <body> <div class="main"> <div class="container"> <div> <i class="iconfont icon-shangchuan"></i> <i class="text">将目录或者多个文件拖拽到此进行扫描</i><br> <i class="text">支持的文件类型:.jpg、jpeg、.bmp、webp、.gif、.png</i><br> <i class="text">每个文件允许的最大尺寸:1M</i> </div> </div> <!--multiple属性则允许用户同时选择多个文件--> <button onclick="chooseFile.click()" class="my_click">选择文件</button> <button onclick="chooseDir.click()" class="my_click">选择文件夹</button> <input id="chooseFile" type="file" multiple onchange="handleFileUpload()"> <input id="chooseDir" type="file" webkitdirectory mozdirectory odirectory onchange="handleDirUpload()"> <table id="fileTable"> <tr class="table-header"> <th>文件名</th> <th>类型</th> <th>大小</th> <th>状态</th> <th>操作</th> </tr> </table> <button class="startUploads" onclick="uploadAllFiles()">开始上传</button> </div> </body> <script> const MAX_FILE_SIZE = 1024 * 1024; // 1MB 大小限制 let selectedFiles = []; //所有要上传的文件 const typesList = ['image/jpeg']; const div = document.querySelector('.container'); div.ondragenter = (e) => { e.preventDefault(); console.log('上传文件已经拖入'); }; div.ondragover = (e) => { e.preventDefault(); console.log('上传文件悬停中'); }; function handleNoDataRow() { const table = document.getElementById('fileTable'); const rows = table.rows; let hasData = false; for (let i = 1; i < rows.length; i++) { // 从第二行开始检查,跳过表头 if (rows[i].cells[0].textContent.trim() !== '' && rows.length > 2) { hasData = true; break; } } const noDataRow = document.querySelector('.no-data'); if (!hasData && !noDataRow) { const newNoDataRow = document.createElement('tr'); newNoDataRow.className = 'no-data'; newNoDataRow.style.height = '50px'; const newNoDataCell = document.createElement('td'); newNoDataCell.colSpan = 5; const icon = document.createElement('i'); icon.className = 'iconfont icon-no-data'; newNoDataCell.appendChild(icon); newNoDataRow.appendChild(newNoDataCell); table.appendChild(newNoDataRow); } else if (hasData && noDataRow) { // 如果有数据且存在 no-data 行,则删除 noDataRow.remove(); } } handleNoDataRow(); function removeFileByField(selectedFiles, fieldName, valueToRemove) { return selectedFiles.filter(function (item) { return item[fieldName] !== valueToRemove; }); } function deleteFile(button) { const row = button.parentNode.parentNode; const fileName = row.cells[0].textContent; const xhr = new XMLHttpRequest(); const url = 'http://attic.niuzheng.net/deleteUpload.php?fileName=' + encodeURIComponent(fileName); xhr.open('GET', url, true); xhr.onload = function () { if (xhr.status === 200) { handleNoDataRow(); selectedFiles = removeFileByField(selectedFiles, 'name', fileName) row.parentNode.removeChild(row); } else { alert('删除文件时出错!'); } }; xhr.send(); } function handleDirUpload() { const files = document.getElementById('chooseDir').files; handleChooseFilesOrDir(files) handleNoDataRow(); } function handleFileUpload() { const files = document.getElementById('chooseFile').files; handleChooseFilesOrDir(files) handleNoDataRow(); } function handleChooseFilesOrDir(files){ for (const file of files) { if (!typesList.includes(file.type)) { alert('禁止上传该类型文件!'); continue; } if (file.size > MAX_FILE_SIZE) { alert('文件大小超过限制!'); continue; } selectedFiles.push(file); addFileToTable(file); } } function addFileToTable(file) { const table = document.getElementById('fileTable'); const newRow = table.insertRow(-1); const fileNameCell = newRow.insertCell(0); fileNameCell.textContent = file.name; const fileTypeCell = newRow.insertCell(1); fileTypeCell.textContent = file.type; const fileSizeCell = newRow.insertCell(2); const sizeInM = (file.size / (1024 * 1024)).toFixed(2); fileSizeCell.textContent = `${sizeInM} M`; const fileStatusCell = newRow.insertCell(3); const progressDiv = document.createElement('div'); progressDiv.className = 'progress'; const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; progressBar.style.width = '0%'; const progressPercentage = document.createElement('div'); progressPercentage.className = 'progress-percentage'; progressPercentage.textContent = '0%'; progressPercentage.style.color = '#4CAF50'; progressDiv.appendChild(progressBar); progressDiv.appendChild(progressPercentage); fileStatusCell.appendChild(progressDiv); const fileOperationCell = newRow.insertCell(4); fileOperationCell.innerHTML = `<button class="delete iconfont icon-shanchu1" onclick="deleteFile(this)"></button>`; // 当添加文件时显示表格并隐藏 no-data 元素 document.getElementById('fileTable').style.display = 'table'; } function uploadAllFiles() { if (selectedFiles.length <= 0) { alert('请选择要上传的图片') } for (const file of selectedFiles) { let index = selectedFiles.indexOf(file)+1; let indexRow = document.getElementById('fileTable').rows[index]; if (!indexRow) continue; const xhr = new XMLHttpRequest(); const url = 'http://attic.niuzheng.net/upload.php'; xhr.open('POST', url, true); xhr.upload.addEventListener('progress', (event) => updateUploadProgress(event, indexRow.cells[3].firstChild)); xhr.onload = function () { if (xhr.status === 200) { console.log('Upload succeeded'); } else { console.log('Upload failed'); } }; // 发送文件数据 const data = new FormData(); data.append('file', file); xhr.send(data); // xhr.abort(); } } function updateUploadProgress(event, progressDiv) { if (event.lengthComputable) { const percentComplete = (event.loaded / event.total) * 100; const progressBar = progressDiv.querySelector('.progress-bar'); progressBar.style.width = `${percentComplete}%`; const progressPercentage = progressDiv.querySelector('.progress-percentage'); progressPercentage.textContent = `${percentComplete.toFixed(2)}%`; progressPercentage.style.color = 'white'; } } div.ondrop = (e) => { e.preventDefault(); console.log('上传文件已放开'); const items = e.dataTransfer.items; const queue = []; const processItem = (item) => { const entry = item.webkitGetAsEntry(); if (!entry.isDirectory) { entry.file((file) => { if (!typesList.includes(file.type)) { alert('禁止上传该类型文件!'); return; } if (file.size > MAX_FILE_SIZE) { alert('文件大小超过限制!'); return; } selectedFiles.push(file); addFileToTable(file); }); } else if (entry.isDirectory) { queue.push(entry) } } const processQueue = () => { while (queue.length > 0) { const entry = queue.shift(); const reader = entry.createReader(); reader.readEntries(entries => { for (const subEntry of entries) { if (subEntry.isFile) { subEntry.file((file) => { selectedFiles.push(file); addFileToTable(file); }); } else if (subEntry.isDirectory) { queue.push(subEntry); } } processQueue(); }); } }; for (const item of items) { processItem(item); } processQueue(); }; </script> </html>

获取完整代码
关注

公共号,回复 rule20240228 获取。

Last modification:August 7, 2024
如果觉得我的文章对你有用,请随意赞赏