在这里插入图片描述

文件夹拖放上传系统(保持文件结构)

下面是一个完整的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…