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

选择多个文件
在 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

上传中
uo1.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
如果觉得我的文章对你有用,请随意赞赏