【親測可用】html+css3+ajax+php文件夾拖放上傳系統(保持文件結構上傳)

在這里插入圖片描述

文件夾拖放上傳系統(保持文件結構)

下面是一個完整的HTML5+CSS3+AJAX+PHP實現,支持拖放文件夾上傳并保持原有文件結構的解決方案。

前端部分 (index.html)

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件夾拖放上傳</title><style>* {box-sizing: border-box;font-family: Arial, sans-serif;}body {margin: 0;padding: 20px;background-color: #f5f5f5;}.container {max-width: 800px;margin: 0 auto;background: white;padding: 20px;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);}h1 {text-align: center;color: #333;}.upload-area {border: 2px dashed #ccc;border-radius: 8px;padding: 40px;text-align: center;margin: 20px 0;transition: all 0.3s;background-color: #fafafa;}.upload-area.highlight {border-color: #4CAF50;background-color: #e8f5e9;}.upload-area p {margin: 0;font-size: 18px;color: #666;}.upload-area .icon {font-size: 48px;color: #4CAF50;margin-bottom: 15px;}.progress-container {margin-top: 20px;display: none;}.progress-bar {height: 20px;background-color: #e0e0e0;border-radius: 4px;margin-bottom: 10px;overflow: hidden;}.progress {height: 100%;background-color: #4CAF50;width: 0%;transition: width 0.3s;}.status {font-size: 14px;color: #666;}.file-list {margin-top: 20px;border-top: 1px solid #eee;padding-top: 20px;}.file-list h3 {margin-top: 0;color: #333;}.file-list ul {list-style: none;padding: 0;max-height: 300px;overflow-y: auto;}.file-list li {padding: 8px 0;border-bottom: 1px solid #eee;color: #555;font-family: monospace;}.btn {background-color: #4CAF50;color: white;border: none;padding: 10px 20px;text-align: center;text-decoration: none;display: inline-block;font-size: 16px;margin: 10px 2px;cursor: pointer;border-radius: 4px;transition: background-color 0.3s;}.btn:hover {background-color: #45a049;}.btn:disabled {background-color: #cccccc;cursor: not-allowed;}</style>
</head>
<body><div class="container"><h1>文件夾拖放上傳</h1><div class="upload-area" id="dropArea"><div class="icon">📁</div><p>拖放文件夾到此處</p><p><small></small></p><button id="browseBtn" class="btn">選擇文件夾</button><input type="file" id="folderInput" webkitdirectory directory multiple style="display: none;"></div><div class="progress-container" id="progressContainer"><div class="progress-bar"><div class="progress" id="progressBar"></div></div><div class="status" id="statusText">準備上傳...</div></div><div class="file-list"><h3>文件結構預覽</h3><ul id="fileList"></ul></div><button id="uploadBtn" class="btn" disabled>開始上傳</button></div><script>document.addEventListener('DOMContentLoaded', function() {const dropArea = document.getElementById('dropArea');const folderInput = document.getElementById('folderInput');const browseBtn = document.getElementById('browseBtn');const uploadBtn = document.getElementById('uploadBtn');const progressContainer = document.getElementById('progressContainer');const progressBar = document.getElementById('progressBar');const statusText = document.getElementById('statusText');const fileList = document.getElementById('fileList');let files = [];let fileStructure = {};// 阻止默認拖放行為['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {dropArea.addEventListener(eventName, preventDefaults, false);document.body.addEventListener(eventName, preventDefaults, false);});// 高亮顯示拖放區域['dragenter', 'dragover'].forEach(eventName => {dropArea.addEventListener(eventName, highlight, false);});['dragleave', 'drop'].forEach(eventName => {dropArea.addEventListener(eventName, unhighlight, false);});// 處理文件放置dropArea.addEventListener('drop', handleDrop, false);// 瀏覽文件夾按鈕browseBtn.addEventListener('click', () => folderInput.click());// 文件夾選擇變化folderInput.addEventListener('change', handleFolderSelect, false);// 上傳按鈕uploadBtn.addEventListener('click', startUpload);function preventDefaults(e) {e.preventDefault();e.stopPropagation();}function highlight() {dropArea.classList.add('highlight');}function unhighlight() {dropArea.classList.remove('highlight');}function handleDrop(e) {const dt = e.dataTransfer;const items = dt.items;files = [];fileStructure = {};fileList.innerHTML = '';// 檢查是否支持目錄上傳if (items && items.length && 'webkitGetAsEntry' in items[0]) {processItems(items);} else {statusText.textContent = '您的瀏覽器不支持文件夾上傳,請使用選擇文件夾按鈕';}}function handleFolderSelect(e) {files = [];fileStructure = {};fileList.innerHTML = '';if (e.target.files.length) {processFileList(e.target.files);}}function processItems(items) {let remaining = items.length;for (let i = 0; i < items.length; i++) {const item = items[i].webkitGetAsEntry();if (item) {scanEntry(item);} else {remaining--;if (remaining === 0) {updateUI();}}}}function scanEntry(entry, path = '') {if (entry.isFile) {entry.file(file => {file.relativePath = path + file.name;files.push(file);// 構建文件結構const pathParts = file.relativePath.split('/');let currentLevel = fileStructure;for (let i = 0; i < pathParts.length - 1; i++) {const part = pathParts[i];if (!currentLevel[part]) {currentLevel[part] = {};}currentLevel = currentLevel[part];}currentLevel[pathParts[pathParts.length - 1]] = 'file';if (--remainingFiles === 0) {updateUI();}});} else if (entry.isDirectory) {const dirReader = entry.createReader();let entries = [];const readEntries = () => {dirReader.readEntries(results => {if (results.length) {entries = entries.concat(Array.from(results));readEntries();} else {// 構建目錄結構const pathParts = (path + entry.name).split('/');let currentLevel = fileStructure;for (let i = 0; i < pathParts.length; i++) {const part = pathParts[i];if (!currentLevel[part]) {currentLevel[part] = {};}currentLevel = currentLevel[part];}remainingDirs += entries.length;remainingFiles += entries.length;for (let i = 0; i < entries.length; i++) {const newPath = path + entry.name + '/';scanEntry(entries[i], newPath);}if (--remainingDirs === 0 && remainingFiles === 0) {updateUI();}}});};readEntries();}}let remainingFiles = 0;let remainingDirs = 0;function processFileList(fileList) {files = Array.from(fileList);fileStructure = {};for (const file of files) {const pathParts = file.webkitRelativePath.split('/');let currentLevel = fileStructure;for (let i = 0; i < pathParts.length - 1; i++) {const part = pathParts[i];if (!currentLevel[part]) {currentLevel[part] = {};}currentLevel = currentLevel[part];}currentLevel[pathParts[pathParts.length - 1]] = 'file';}updateUI();}function updateUI() {// 顯示文件結構renderFileStructure(fileStructure, '');if (files.length > 0) {uploadBtn.disabled = false;statusText.textContent = `準備上傳 ${files.length} 個文件`;} else {uploadBtn.disabled = true;statusText.textContent = '沒有可上傳的文件';}}function renderFileStructure(structure, path, indent = 0) {for (const key in structure) {const li = document.createElement('li');li.style.paddingLeft = `${indent * 20}px`;if (structure[key] === 'file') {li.textContent = `📄 ${key}`;fileList.appendChild(li);} else {li.textContent = `📁 ${key}`;fileList.appendChild(li);renderFileStructure(structure[key], path + key + '/', indent + 1);}}}function startUpload() {if (files.length === 0) return;progressContainer.style.display = 'block';uploadBtn.disabled = true;const totalFiles = files.length;let uploaded = 0;statusText.textContent = `上傳中: 0/${totalFiles}`;function uploadNext() {if (uploaded >= totalFiles) {statusText.textContent = `上傳完成! 共上傳 ${totalFiles} 個文件`;uploadBtn.disabled = false;return;}const file = files[uploaded];const formData = new FormData();formData.append('file', file);formData.append('path', file.webkitRelativePath || file.relativePath);const xhr = new XMLHttpRequest();xhr.open('POST', 'upload.php', true);xhr.upload.onprogress = function(e) {if (e.lengthComputable) {const fileProgress = (e.loaded / e.total) * (100 / totalFiles);const totalProgress = (uploaded / totalFiles) * 100 + fileProgress;progressBar.style.width = `${totalProgress}%`;}};xhr.onload = function() {if (xhr.status === 200) {uploaded++;statusText.textContent = `上傳中: ${uploaded}/${totalFiles}`;uploadNext();} else {statusText.textContent = `上傳失敗: ${xhr.responseText}`;uploadBtn.disabled = false;}};xhr.onerror = function() {statusText.textContent = '上傳出錯,請重試';uploadBtn.disabled = false;};xhr.send(formData);}uploadNext();}});</script>
</body>
</html>

后端部分 (upload.php)

<?php
header('Content-Type: application/json');// 配置上傳目錄
$uploadDir = __DIR__ . '/uploads/';// 確保上傳目錄存在
if (!file_exists($uploadDir)) {mkdir($uploadDir, 0777, true);
}// 檢查是否有文件上傳
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {http_response_code(400);echo json_encode(['error' => '沒有文件被上傳或上傳出錯']);exit;
}// 獲取文件路徑信息
$relativePath = isset($_POST['path']) ? $_POST['path'] : '';
$relativePath = ltrim($relativePath, '/');// 防止目錄遍歷攻擊
if (strpos($relativePath, '..') !== false) {http_response_code(400);echo json_encode(['error' => '非法路徑']);exit;
}// 創建完整的目錄結構
$fullPath = $uploadDir . $relativePath;
$directory = dirname($fullPath);if (!file_exists($directory)) {if (!mkdir($directory, 0777, true)) {http_response_code(500);echo json_encode(['error' => '無法創建目錄']);exit;}
}// 移動上傳的文件
if (move_uploaded_file($_FILES['file']['tmp_name'], $fullPath)) {echo json_encode(['success' => true, 'path' => $relativePath]);
} else {http_response_code(500);echo json_encode(['error' => '文件移動失敗']);
}

功能說明

  1. 前端功能

    • 支持拖放文件夾上傳
    • 支持通過按鈕選擇文件夾
    • 實時顯示文件結構預覽
    • 顯示上傳進度條
    • 保持原始文件目錄結構
  2. 后端功能

    • 接收上傳的文件
    • 根據相對路徑創建目錄結構
    • 防止目錄遍歷攻擊
    • 返回上傳狀態

部署說明

  1. 將這兩個文件放在同一目錄下
  2. 確保PHP有寫入權限(需要創建uploads目錄)
  3. 確保服務器支持PHP文件上傳
  4. 現代瀏覽器訪問index.html即可使用

注意事項

  1. 文件夾上傳需要現代瀏覽器支持(Chrome、Edge、Firefox等)
  2. 大文件上傳可能需要調整PHP配置(upload_max_filesize, post_max_size等)
  3. 生產環境應考慮添加更多安全措施,如文件類型檢查、用戶認證等
  4. 對于超大文件或大量文件,可能需要分塊上傳實現

這個實現完全使用原生技術,不依賴任何第三方庫,保持了原始文件目錄結構。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/87253.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/87253.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/87253.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是數據清洗?數據清洗有哪些步驟?

目錄 一、數據清洗的定義和重要性 1. 數據清洗的定義 2. 數據清洗的重要性 二、數據清洗的前期準備 1. 明確清洗目標 2. 了解數據來源和背景 3. 制定清洗計劃 三、數據清洗的具體步驟 1. 數據審計 2. 處理缺失值 3. 處理重復值 4. 處理異常值 5. 數據標準化 6. 數…

Vue3+TypeScript中v-bind()的原理與用法

在 Vue 3 的單文件組件&#xff08;SFC&#xff09;中&#xff0c;v-bind() 用于在 <style> 塊中動態綁定 CSS 值到組件的響應式數據&#xff0c;實現了狀態驅動樣式的能力。下面詳細講解其原理和用法&#xff1a; 一、核心原理 CSS 變量注入 Vue 編譯器會將 v-bind() 轉…

2 geotools入門示例

1. 設置 Spring Boot 項目并集成 GeoTools 依賴 首先&#xff0c;你需要創建一個新的 Spring Boot 項目。你可以使用 Spring Initializr 來快速生成項目骨架。 選擇以下依賴&#xff1a; Web: Spring Web (用于創建 REST API)Developer Tools: Spring Boot DevTools (可選&a…

深度解析String不可變性:從Java底層到設計哲學

一、String不可變性的直觀理解 在Java中,String對象一旦創建,其內容就不可更改。任何看似"修改"String的操作,實際上都是創建了一個全新的String對象。這種設計是Java語言基礎架構的重要部分,理解其底層原理對編寫高效、安全的Java程序至關重要。 String str =…

C++并發編程-2.C++ 線程管控

參考&#xff1a;https://llfc.club/category?catid225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2Tuk4RfvfBC788LlqnQrWiPiEGW 1. 簡歷 本節介紹C線程管控&#xff0c;包括移交線程的歸屬權&#xff0c;線程并發數量控制以及獲取線程id等基本操作。 2. 線程歸屬權 比如下面&#xff…

Qt面試常問

1.QT信號與槽的底層原理&#xff1f; 底層通過元對象系統和事件循環完成的&#xff0c;能夠在運行期間動態處理信號槽之間的連接與斷開&#xff0c;而不是像函數調用那樣在編譯期間就完全確定了。元對象系統包含&#xff1a;QObject類、Q_OBJECT宏定義、moc編譯器當發送一個信…

【git】錯誤

【成功解決】開代理 unable to access ‘https://github.com/laigeoffer/pmhub.git/’: Recv failure: Connection was reset

什么是狀態機?狀態機入門

狀態機&#xff1a;優雅管理復雜邏輯的Python實踐 在軟件開發中&#xff0c;狀態機&#xff08;Finite State Machine, FSM&#xff09; 是管理多狀態轉換的利器。它將行為分解為離散的狀態、事件和轉移規則&#xff0c;大幅提升代碼的可讀性與可維護性。本文通過Python示例解析…

【Python打卡Day41】簡單CNN@浙大疏錦行

可以看到即使在深度神經網絡情況下&#xff0c;準確率仍舊較差&#xff0c;這是因為特征沒有被有效提取----真正重要的是特征的提取和加工過程。MLP把所有的像素全部展平了&#xff08;這是全局的信息&#xff09;&#xff0c;無法布置到局部的信息&#xff0c;所以引入了卷積神…

MySQL中InnoDB存儲引擎底層原理與MySQL日志機制深入解析

MySQL的內部組件結構如下&#xff1a; 大體來說&#xff0c;MySQL 可以分為 Server 層和存儲引擎層兩部分。 Server層 主要包括連接器、查詢緩存、分析器、優化器、執行器等&#xff0c;涵蓋 MySQL 的大多數核心服務功能&#xff0c;以及所有的內置函數&#xff08;如日期、…

MCP基本概念

基本概念 現在大模型交互的熱門形式&#xff1a; 第一、Agent與Tools(工具)的交互Agent需要調用外部工具和APl、訪問數據庫、執行代碼等。> MCP 第二、Agent與Agent(其他智能體或用戶)的交互Agent需要理解其他Agent的意圖、協同完成任務、與用戶進行自然的對話。 > A2A…

Docker容器相關命令介紹和示例

Docker 容器是鏡像的運行實例。以下是常用的 Docker 容器命令及其示例&#xff1a; 1. 運行容器 docker run [選項] <鏡像名> [命令]常用選項&#xff1a; -d&#xff1a;后臺運行&#xff08;守護模式&#xff09;-it&#xff1a;交互式終端--name&#xff1a;指定容…

【Akshare】高效下載股票和ETF數據

在量化投資與金融數據分析的世界里&#xff0c;獲取高質量的市場數據是構建有效策略的關鍵。Python庫Akshare為我們提供了一個強大且易于使用的接口&#xff0c;可以輕松地從網絡上抓取各類金融數據。本文將詳細介紹如何利用Akshare下載股票和ETF的歷史行情數據。 安裝Akshare…

分布式--3--分布式事務

1 簡介 事務在單系統中的表現&#xff1a;多次數據庫操作用事務進行管理&#xff0c;來保證ACID原則。 但是如果各個模塊都是單獨獨立出來的微服務&#xff0c;進行了分布式部署&#xff0c;單系統里的事務將不能保證各個數據庫操作的一致性&#xff0c;因此就需要分布式事務來…

不同建模方式的介紹 RTL建模筆記(1)

說明&#xff1a;該專欄"RTL建模筆記"是《RTL Modeling with SystemVerilog for Simulation and Synthesis》的翻譯&#xff1b;該筆記略過了第一章第一小節中背景介紹內容&#xff0c;以及第二小節前面部分的門級、RTL級建模介紹&#xff0c;對于后續學習不影響。 …

<13>-MySQL用戶管理

目錄 一&#xff0c;用戶管理操作 1&#xff0c;創建用戶 2&#xff0c;查詢用戶 3&#xff0c;修改密碼 4&#xff0c;刪除用戶 二&#xff0c;數據庫權限 1&#xff0c;用戶授權 2&#xff0c;回收權限 一&#xff0c;用戶管理操作 1&#xff0c;創建用戶 --創建用戶…

如何使用超低噪聲電源提高AD 時鐘電路質量,改善超聲系統的圖像質量

超聲波技術是醫療診斷和其他應用中廣泛使用的無創工具&#xff0c;已經從靜態圖像進化到動態圖像&#xff0c;從黑白呈現變為彩色多普勒圖像。這些重大進步主要是由于引入了數字超聲技術。雖然這些進步提高了超聲成像的有效性和通用性&#xff0c;但同樣重要的是&#xff0c;這…

【解決方案】Kali 2022.3修復倉庫密鑰一鍵安裝docker,docker compose

1、Kali 2022.3 2、一鍵安裝docker&#xff0c;docker compose #!/bin/bashecho " 安全的Kali Docker安裝腳本 "# 備份重要配置 cp /etc/apt/sources.list /etc/apt/sources.list.backup.$(date %Y%m%d)# 修復Kali倉庫配置 echo "修復Kali倉庫配置..." ca…

Transformer、RNN (循環神經網絡) 和 CNN (卷積神經網絡)的區別

我們來詳細對比一下 Transformer、RNN (循環神經網絡) 和 CNN (卷積神經網絡) 這三種在深度學習中極其重要的架構&#xff0c;并通過具體例子說明它們的區別。 核心區別總結&#xff1a; 處理數據的方式&#xff1a; CNN: 專注于局部特征和空間/時間模式。通過卷積核在輸入數據…

408第二季 - 組成原理 - 數據類型轉換

這章內容會比較少 閑聊 如果題目說把8位改成4位&#xff0c;你保留低位就行了 這里保留的是0101 然后是有符號數和無符號數的轉換 機器數就是二進制長什么樣子 然后就是小數點是不參與存儲的 然后簡單看看代碼 這是short就說明是有符號數 unsigned就是說明是無符號數 然后y…