FTP局域网小网站V2.3——安全、批量、有序,体验全新进化

前言

还记得那个在局域网内即开即用、为你省去文件传输烦恼的FTP小工具吗?在收获了许多朋友的积极反馈后,我们迎来了又一次重要更新!V2.3版本聚焦于大家最关注的三个核心诉求:安全、效率和条理,致力于让你的文件共享体验既强大又省心。

(note: 其他局域网内的设备,请访问 192.168.xx.xx:5000那个地址即可。)

本次升级主要包含三大核心功能:

🔐 密钥验证,为隐私加把锁:彻底告别"网络邻居皆可访问"的裸奔时代!我们为网站增加了简单的API密钥验证机制。现在,只有持有密钥的设备才能接入,有效防止在公共网络环境下的未授权访问,让你的私人文件更加安全。

🚀 批量上传,效率飞跃:一次选中上百张照片或大量文件,即可自动完成上传!我们优化了上传逻辑,无论是备份手机相册还是同步工作资料,都能大幅节省你的时间和操作步骤。

📂 下载页分类,井然有序:面对海量文件不再迷茫。下载页面新增了文件分类功能,你可以清晰地从不同类别中快速定位所需,让文件管理变得条理清晰。

此次更新,标志着我们这个轻量级工具从"可用"向"好用、安心"迈出了坚实的一步。知足而上进,温柔且坚定,我们将继续用代码优化身边的世界。

友好提示:大家也可以把想要新增的功能写在评论区上,我有空会查看评论区,有空闲时间也会适当更新一版,让我们的工具更好用。

希望V2.3版本能给你带来更出色的体验!

往期版本

一步一个脚印,up本身工作也比较忙,只好空闲抽点娱乐时间来谢谢咦~友友们见谅.

效果展示

手机端

进入时需要输入密钥,密钥在app.py中确定

最大传输文件大小限制在upload.html中修改

上传成功后会有图标提示

下载页面, 暂时是按照类型分类,若友友们有什么好提议,可以在评论区和我说。

展示的文件名过长的话,你们可以使用横屏,把手机横过来看

电脑端

大部分差不多,我就不一一列了。可以参考下之前版本的

最大传输文件大小限制在upload.html中修改

文件名过长我这里加了个小滑杆。

代码

download_page.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>下载文件 - FTP功能小网站</title>
    <!-- 引入Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    
    <!-- 配置Tailwind自定义颜色 -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4F46E5',
                    }
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .file-item-hover {
                @apply bg-blue-50;
            }
        }
    </style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-teal-50 min-h-screen font-sans text-gray-800">
    <div class="container mx-auto px-4 py-8 max-w-4xl relative">
        <!-- 登出按钮 -->
        <div class="absolute top-0 right-0 mt-2 mr-2">
            <a href="{{ url_for('logout') }}" class="inline-flex items-center text-gray-500 hover:text-primary transition-colors px-3 py-2 rounded-lg hover:bg-primary/5">
                <i class="fa fa-sign-out mr-1"></i>
                <span>登出</span>
            </a>
        </div>
        
        <!-- 页面标题 -->
        <header class="text-center mb-10 mt-6">
            <h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-primary mb-2">
                <i class="fa fa-cloud-download mr-3"></i>文件下载
            </h1>
            <p class="text-gray-500">选择您需要下载的文件</p>
        </header>
        
        <!-- 面包屑导航 -->
        <div class="mb-4">
            <nav class="flex" aria-label="Breadcrumb">
                <ol class="inline-flex items-center space-x-1 md:space-x-3">
                    <!-- 根目录 -->
                    <li>
                        <a href="{{ url_for('download_page') }}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-primary">
                            <i class="fa fa-folder mr-2"></i>
                            <span>文件</span>
                        </a>
                    </li>
                    <!-- 子目录 -->
                    {% if breadcrumbs %}
                        {% for crumb in breadcrumbs %}
                        <li>
                            <div class="flex items-center">
                                <i class="fa fa-chevron-right text-gray-400 mx-2 text-xs"></i>
                                <a href="{{ url_for('download_page', dir=crumb.path) }}" class="text-sm font-medium text-gray-700 hover:text-primary">
                                    {{ crumb.name }}
                                </a>
                            </div>
                        </li>
                        {% endfor %}
                    {% endif %}
                </ol>
            </nav>
        </div>
        
        <!-- 文件列表卡片 -->
        <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
            <!-- 列表头部 -->
            <div class="bg-gray-50 px-6 py-4 border-b border-gray-100">
                <h2 class="font-semibold text-gray-700">
                    {% if current_dir %}
                        {{ current_dir.split('/')[-1] }} 中的内容
                    {% else %}
                        可供下载的文件
                    {% endif %}
                </h2>
            </div>
            
            <!-- 文件列表 -->
            <div class="divide-y divide-gray-100">
                <!-- 显示文件夹 -->
                {% if folders and folders|length > 0 %}
                    {% for folder in folders %}
                    <div class="px-6 py-4 flex items-center justify-between transition-all duration-200 hover:file-item-hover">
                        <!-- 文件夹名 -->
                        <a href="{{ url_for('download_page', dir=current_dir ~ '\\' ~ folder if current_dir else folder) }}" class="flex items-center flex-1 mr-4" title="{{ folder }}">
                            <div class="w-10 h-10 rounded bg-blue-100 flex items-center justify-center mr-4 text-blue-500 flex-shrink-0">
                                <i class="fa fa-folder"></i>
                            </div>
                            <div class="overflow-x-auto whitespace-nowrap flex-1 max-w-xs sm:max-w-md scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent">
                                <span class="font-medium text-gray-800 inline-block">{{ folder }}</span>
                            </div>
                        </a>
                        
                        <!-- 进入按钮 -->
                        <a href="{{ url_for('download_page', dir=current_dir ~ '\\' ~ folder if current_dir else folder) }}" 
                           class="inline-flex items-center bg-gray-100 text-gray-700 px-3 py-1 rounded-lg transition-all duration-200 hover:bg-gray-200">
                            <i class="fa fa-arrow-right mr-1"></i>
                            <span>进入</span>
                        </a>
                    </div>
                    {% endfor %}
                {% endif %}
                
                <!-- 显示文件 -->
                {% if files and files|length > 0 %}
                    {% for file in files %}
                    <div class="px-6 py-4 flex items-center justify-between transition-all duration-200 hover:file-item-hover">
                        <!-- 文件名 -->
                        <div class="flex items-center flex-1 pr-4">
                            <div class="w-10 h-10 rounded bg-gray-100 flex items-center justify-center mr-4 text-gray-500 flex-shrink-0">
                                <i class="fa fa-file-o"></i>
                            </div>
                            <div class="overflow-x-auto whitespace-nowrap flex-1 max-w-xs sm:max-w-md scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent">
                                <span class="font-medium text-gray-800 inline-block" title="{{ file }}">{{ file }}</span>
                            </div>
                        </div>
                        
                        <!-- 下载按钮 -->
                        <a href="{{ url_for('download_file', filename=file, dir=current_dir) }}" 
                           class="inline-flex items-center bg-primary/10 text-primary px-4 py-2 rounded-lg transition-all duration-200 hover:bg-primary hover:text-white">
                            <i class="fa fa-download mr-2"></i>
                            <span>下载</span>
                        </a>
                    </div>
                    {% endfor %}
                {% endif %}
                
                <!-- 空状态 -->
                {% if (not folders or folders|length == 0) and (not files or files|length == 0) %}
                    <div class="px-6 py-16 text-center">
                        <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-gray-100 text-gray-400 mb-4">
                            <i class="fa fa-folder-open-o text-2xl"></i>
                        </div>
                        <p class="text-gray-500">当前目录为空</p>
                    </div>
                {% endif %}
            </div>
        </div>
        
        <!-- 返回按钮 -->
        <div class="text-center">
            <a href="{{ url_for('home') }}" class="inline-flex items-center text-primary hover:text-primary/80 transition-colors">
                <i class="fa fa-arrow-left mr-2"></i>返回首页
            </a>
        </div>
        
        <!-- 页脚 -->
        <footer class="mt-16 text-center text-gray-400 text-sm">
            <p>© 2025 FTP功能小网站 - 便捷快速的文件传输服务 - AndyDennis</p>
        </footer>
    </div>
    
    <!-- 简单的动画效果 -->
    <script>
        // 添加页面加载动画
        document.addEventListener('DOMContentLoaded', () => {
            const fileItems = document.querySelectorAll('.divide-y > div');
            fileItems.forEach((item, index) => {
                setTimeout(() => {
                    item.classList.add('animate-fade-in');
                }, 100 * index);
            });
        });
        
        // 添加动画样式
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(10px); }
                to { opacity: 1; transform: translateY(0); }
            }
            .animate-fade-in {
                animation: fadeIn 0.3s ease-out forwards;
            }
        `;
        document.head.appendChild(style);
    </script>
</body>
</html>

home.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FTP功能小网站</title>
    <!-- 引入Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    
    <!-- 配置Tailwind自定义颜色和字体 -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4F46E5', // 主色调:靛蓝色,传达信任和专业感
                        secondary: '#8B5CF6', // 辅助色:紫色
                        neutral: '#F8FAFC', // 中性色:浅灰
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .btn-hover {
                @apply transform transition-all duration-300 hover:scale-105 hover:shadow-lg;
            }
            .card-effect {
                @apply bg-white rounded-xl shadow-md transition-all duration-300 hover:shadow-xl;
            }
        }
    </style>
</head>
<body class="bg-gradient-to-br from-neutral to-blue-50 min-h-screen flex flex-col items-center justify-center p-4 font-sans text-gray-800">
    <!-- 页面容器 -->
    <div class="max-w-md w-full mx-auto text-center relative">
        
        <!-- 标题区域 -->
        <div class="mb-12 animate-fade-in">
            <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
                <i class="fa fa-cloud-upload text-3xl"></i>
            </div>
            <h1 class="text-[clamp(2rem,5vw,3rem)] font-bold text-primary mb-2">FTP功能小网站</h1>
            <p class="text-gray-500">简单高效的文件上传与下载服务</p>
        </div>
        
        <!-- 操作按钮区域 -->
        <div class="grid gap-6">
            <!-- 上传按钮 -->
            <a href="{{ url_for('upload_file') }}" class="card-effect p-6 flex items-center justify-center space-x-3 btn-hover group">
                <div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center text-green-600 group-hover:bg-green-600 group-hover:text-white transition-colors">
                    <i class="fa fa-upload"></i>
                </div>
                <span class="text-lg font-medium">上传文件</span>
            </a>
            
            <!-- 下载按钮 -->
            <a href="{{ url_for('download_page') }}" class="card-effect p-6 flex items-center justify-center space-x-3 btn-hover group">
                <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-colors">
                    <i class="fa fa-download"></i>
                </div>
                <span class="text-lg font-medium">下载文件</span>
            </a>
        </div>
        
        <!-- 页脚 -->
        <footer class="mt-16 text-gray-400 text-sm">
            <p>© 2025 FTP功能小网站 - 便捷快速的文件传输服务 - AndyDennis</p>
        </footer>
    </div>
    
    <!-- 简单的动画效果脚本 -->
    <script>
        // 添加页面加载动画类
        document.addEventListener('DOMContentLoaded', () => {
            const cards = document.querySelectorAll('.card-effect');
            cards.forEach((card, index) => {
                setTimeout(() => {
                    card.classList.add('animate-slide-up');
                }, 100 * index);
            });
        });
        
        // 定义简单的CSS动画
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(10px); }
                to { opacity: 1; transform: translateY(0); }
            }
            @keyframes slideUp {
                from { opacity: 0; transform: translateY(20px); }
                to { opacity: 1; transform: translateY(0); }
            }
            .animate-fade-in {
                animation: fadeIn 0.6s ease-out forwards;
            }
            .animate-slide-up {
                animation: slideUp 0.5s ease-out forwards;
            }
        `;
        document.head.appendChild(style);
    </script>
</body>
</html>

login.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录 - FTP功能小网站</title>
    <!-- 引入Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    
    <!-- 配置Tailwind自定义颜色和字体 -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4F46E5', // 主色调:靛蓝色,传达信任和专业感
                        secondary: '#8B5CF6', // 辅助色:紫色
                        neutral: '#F8FAFC', // 中性色:浅灰
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .btn-hover {
                @apply transform transition-all duration-300 hover:scale-105 hover:shadow-lg;
            }
            .card-effect {
                @apply bg-white rounded-xl shadow-md transition-all duration-300 hover:shadow-xl;
            }
        }
    </style>
</head>
<body class="bg-gradient-to-br from-neutral to-blue-50 min-h-screen flex flex-col items-center justify-center p-4 font-sans text-gray-800">
    <!-- 页面容器 -->
    <div class="max-w-md w-full mx-auto text-center">
        <!-- 标题区域 -->
        <div class="mb-12 animate-fade-in">
            <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
                <i class="fa fa-lock text-3xl"></i>
            </div>
            <h1 class="text-[clamp(2rem,5vw,3rem)] font-bold text-primary mb-2">请输入密钥</h1>
            <p class="text-gray-500">需要验证身份才能访问FTP功能</p>
        </div>
        
        <!-- 登录表单 -->
        <div class="card-effect p-8 animate-slide-up">
            {% if error %}
            <div class="mb-4 p-3 bg-red-100 text-red-700 rounded-lg">
                {{ error }}
            </div>
            {% endif %}
            <form method="POST" action="{{ url_for('login') }}">
                <div class="mb-6">
                    <label for="key" class="block text-left text-gray-700 mb-2">密钥</label>
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
                            <i class="fa fa-key"></i>
                        </span>
                        <input type="password" id="key" name="key" 
                               class="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors"
                               placeholder="请输入访问密钥" required>
                    </div>
                </div>
                <button type="submit" 
                        class="w-full bg-primary text-white py-3 px-6 rounded-lg font-medium btn-hover flex items-center justify-center space-x-2">
                    <i class="fa fa-sign-in"></i>
                    <span>验证密钥</span>
                </button>
            </form>
        </div>
        
        <!-- 页脚 -->
        <footer class="mt-16 text-gray-400 text-sm">
            <p>© 2025 FTP功能小网站 - 便捷快速的文件传输服务 - AndyDennis</p>
        </footer>
    </div>
    
    <!-- 简单的动画效果脚本 -->
    <script>
        // 添加页面加载动画类
        document.addEventListener('DOMContentLoaded', () => {
            const cards = document.querySelectorAll('.card-effect');
            cards.forEach((card, index) => {
                setTimeout(() => {
                    card.classList.add('animate-slide-up');
                }, 100 * index);
            });
        });
        
        // 定义简单的CSS动画
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(10px); }
                to { opacity: 1; transform: translateY(0); }
            }
            @keyframes slideUp {
                from { opacity: 0; transform: translateY(20px); }
                to { opacity: 1; transform: translateY(0); }
            }
            .animate-fade-in {
                animation: fadeIn 0.6s ease-out forwards;
            }
            .animate-slide-up {
                animation: slideUp 0.5s ease-out forwards;
            }
        `;
        document.head.appendChild(style);
    </script>
</body>
</html>

upload_success.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传成功 - FTP功能小网站</title>
    <!-- 引入Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    
    <!-- 配置Tailwind自定义颜色 -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4F46E5',
                        success: '#10B981', // 成功状态颜色
                    }
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .btn-action {
                @apply px-6 py-3 rounded-lg font-medium transition-all duration-300 hover:shadow-lg transform hover:-translate-y-0.5;
            }
            .btn-primary {
                @apply bg-primary text-white hover:bg-primary/90;
            }
            .btn-secondary {
                @apply bg-gray-100 text-gray-700 hover:bg-gray-200;
            }
        }
    </style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-teal-50 min-h-screen font-sans flex items-center justify-center p-4">
    <div class="max-w-md w-full text-center relative">
        <!-- 登出按钮 -->
        <div class="absolute top-0 right-0 mt-2 mr-2">
            <a href="{{ url_for('logout') }}" class="inline-flex items-center text-gray-500 hover:text-primary transition-colors px-3 py-2 rounded-lg hover:bg-primary/5">
                <i class="fa fa-sign-out mr-1"></i>
                <span>登出</span>
            </a>
        </div>
        
        <!-- 成功状态卡片 -->
        <div class="bg-white rounded-2xl shadow-lg p-8 md:p-10 transform transition-all duration-500 animate-float">
            <!-- 成功图标 -->
            <div class="w-20 h-20 mx-auto bg-success/10 rounded-full flex items-center justify-center mb-6 animate-pulse-slow">
                <i class="fa fa-check text-4xl text-success"></i>
            </div>
            
            <!-- 成功信息 -->
            <h1 class="text-[clamp(1.5rem,3vw,2rem)] font-bold text-gray-800 mb-3">上传成功!</h1>
            {% if filename %}
                {% if filename is iterable and filename is not string %}
                    <p class="text-gray-600 mb-8">
                        以下文件已成功上传:
                    </p>
                    
                    <!-- 文件信息 -->
                    <div class="bg-gray-50 rounded-lg p-4 mb-8 text-left">
                        {% for file in filename %}
                            <div class="flex items-center text-sm text-gray-500 mb-2">
                                <i class="fa fa-file-text-o w-5 text-gray-400"></i>
                                <span class="ml-2 truncate">{{ file }}</span>
                            </div>
                        {% endfor %}
                    </div>
                {% else %}
                    <p class="text-gray-600 mb-8">
                        文件 <span class="font-medium text-primary">{{ filename }}</span> 已成功上传
                    </p>
                    
                    <!-- 文件信息 -->
                    <div class="bg-gray-50 rounded-lg p-4 mb-8 text-left">
                        <div class="flex items-center text-sm text-gray-500">
                            <i class="fa fa-file-text-o w-5 text-gray-400"></i>
                            <span class="ml-2 truncate">{{ filename }}</span>
                        </div>
                    </div>
                {% endif %}
            {% else %}
                <p class="text-gray-600 mb-8">
                    文件已成功上传
                </p>
            {% endif %}
            
            <!-- 操作按钮 -->
            <div class="flex flex-col sm:flex-row gap-4 justify-center">
                <a href="{{ url_for('upload_file') }}" class="btn-action btn-primary flex items-center justify-center">
                    <i class="fa fa-upload mr-2"></i>上传另一个文件
                </a>
                <a href="{{ url_for('home') }}" class="btn-action btn-secondary flex items-center justify-center">
                    <i class="fa fa-home mr-2"></i>返回首页
                </a>
            </div>
        </div>
        
        <!-- 页脚 -->
        <footer class="mt-10 text-gray-400 text-sm">
            <p>© 2025 FTP功能小网站 - 便捷快速的文件传输服务 - AndyDennis</p>
        </footer>
    </div>
    
    <!-- 动画样式 -->
    <style>
        @keyframes float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-10px); }
        }
        @keyframes pulseSlow {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.05); }
        }
        .animate-float {
            animation: float 3s ease-in-out infinite;
        }
        .animate-pulse-slow {
            animation: pulseSlow 2s ease-in-out infinite;
        }
    </style>
</body>
</html>

upload.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传文件 - FTP功能小网站</title>
    <!-- 引入Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    
    <!-- 引入Dropzone.js CSS -->
    <link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
    
    <!-- 配置Tailwind CSS -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4F46E5',
                        secondary: '#8B5CF6',
                        accent: '#10B981', // 上传相关的强调色(绿色)
                    }
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .file-upload-hover {
                @apply border-accent bg-accent/5;
            }
            .btn-primary {
                @apply bg-primary text-white px-6 py-3 rounded-lg font-medium transition-all duration-300 hover:bg-primary/90 hover:shadow-lg transform hover:-translate-y-0.5;
            }
            .card {
                @apply bg-white rounded-xl shadow-md p-6 md:p-8 transition-all duration-300 hover:shadow-xl;
            }
        }
    </style>
    
    <!-- Dropzone自定义样式 -->
    <style>
        /* 上传区域样式 */
        .dropzone {
            border: 2px dashed #e5e7eb !important;
            border-radius: 0.5rem !important;
            background-color: #ffffff !important;
            transition: all 0.3s ease !important;
        }
        
        .dropzone:hover,
        .dropzone.dz-clickable:hover {
            border-color: #10B981 !important;
            background-color: rgba(16, 185, 129, 0.05) !important;
        }
        
        /* 上传区域文字样式 */
        .dz-message {
            margin: 2rem 0 !important;
            text-align: center !important;
        }
        
        /* 文件预览样式 */
        .dz-preview {
            margin: 0.5rem !important;
            border: 1px solid #e5e7eb !important;
            border-radius: 0.375rem !important;
            background-color: #f9fafb !important;
            padding: 0.75rem !important;
            transition: all 0.2s ease !important;
        }
        
        .dz-preview:hover {
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
        }
        
        /* 文件信息样式 */
        .dz-details {
            margin-bottom: 0.5rem !important;
        }
        
        .dz-filename {
            font-size: 0.875rem !important;
            font-weight: 500 !important;
            color: #374151 !important;
        }
        
        .dz-size {
            font-size: 0.75rem !important;
            color: #6b7280 !important;
            margin-top: 0.25rem !important;
        }
        
        /* 进度条样式 */
        .dz-progress {
            background-color: #e5e7eb !important;
            border-radius: 9999px !important;
            overflow: hidden !important;
            height: 0.5rem !important;
            margin: 0.5rem 0 !important;
        }
        
        .dz-upload {
            background-color: #10B981 !important;
            height: 100% !important;
            border-radius: 9999px !important;
            transition: width 0.15s ease !important;
        }
        
        /* 成功/错误标记样式 */
        .dz-success-mark,
        .dz-error-mark {
            position: absolute !important;
            top: 0.5rem !important;
            right: 0.5rem !important;
            font-size: 1rem !important;
        }
        
        .dz-success-mark {
            color: #10B981 !important;
        }
        
        .dz-error-mark {
            color: #ef4444 !important;
        }
        
        /* 错误消息样式 */
        .dz-error-message {
            font-size: 0.75rem !important;
            color: #ef4444 !important;
            margin-top: 0.25rem !important;
            padding: 0.25rem !important;
            background-color: rgba(239, 68, 68, 0.1) !important;
            border-radius: 0.25rem !important;
        }
        
        /* 移除文件按钮样式 */
        .dz-preview .dz-remove {
            font-size: 0.75rem !important;
            color: #6366f1 !important;
            text-decoration: none !important;
            margin-top: 0.5rem !important;
            display: inline-block !important;
            transition: color 0.2s ease !important;
        }
        
        .dz-preview .dz-remove:hover {
            color: #4f46e5 !important;
            text-decoration: underline !important;
        }
    </style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-teal-50 min-h-screen font-sans">
    <div class="container mx-auto px-4 py-8 max-w-3xl relative">
        <!-- 登出按钮 -->
        <div class="absolute top-0 right-0 mt-2 mr-2">
            <a href="{{ url_for('logout') }}" class="inline-flex items-center text-gray-500 hover:text-primary transition-colors px-3 py-2 rounded-lg hover:bg-primary/5">
                <i class="fa fa-sign-out mr-1"></i>
                <span>登出</span>
            </a>
        </div>
        
        <!-- 页面标题 -->
        <header class="text-center mb-10 mt-6">
            <h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-primary mb-2">
                <i class="fa fa-cloud-upload mr-3"></i>上传文件
            </h1>
            <p class="text-gray-500">选择并上传您需要存储的文件</p>
        </header>
        
        <!-- 上传卡片 -->
        <div class="card mb-8">
            <!-- Dropzone上传区域 -->
            <form action="{{ url_for('upload_success')}}" method="POST" enctype="multipart/form-data" class="dropzone mb-6" id="my-dropzone">
                <div class="dz-message">
                    <i class="fa fa-file-o text-5xl text-gray-400 mb-4"></i>
                    <p class="text-gray-600 mb-4">拖放文件到此处,或</p>
                    <button type="button" class="btn-primary inline-flex items-center">
                        <i class="fa fa-folder-open mr-2"></i>选择文件
                    </button>
                </div>
            </form>
            
            <!-- 上传说明 -->
            <div class="text-sm text-gray-500 bg-gray-50 p-4 rounded-lg">
                <p><i class="fa fa-info-circle text-primary mr-2"></i>上传说明:</p>
                <ul class="list-disc list-inside mt-2 space-y-1">
                    <li>请确保文件符合上传规范</li>
                    <li>支持多种文件格式上传</li>
                    <li>上传完成后将自动跳转至成功页面</li>
                </ul>
            </div>
        </div>
        
        <!-- 返回按钮 -->
        <div class="text-center">
            <a href="{{ url_for('home') }}" class="inline-flex items-center text-primary hover:text-primary/80 transition-colors">
                <i class="fa fa-arrow-left mr-2"></i>返回首页
            </a>
        </div>
        
        <!-- 页脚 -->
        <footer class="mt-16 text-center text-gray-400 text-sm">
            <p>© 2025 FTP功能小网站 - 便捷快速的文件传输服务 - AndyDennis</p>
        </footer>
    </div>
    
    <!-- 引入Dropzone.js JS -->
    <script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
    
    <!-- Dropzone配置脚本 -->
    <script>
        console.log("开始初始化Dropzone.js");
        
        // 确保DOM加载完成
        document.addEventListener('DOMContentLoaded', function() {
            console.log("DOM加载完成,初始化Dropzone");
            
            // 配置Dropzone
            Dropzone.options.myDropzone = {
                paramName: "file", // 与后端接收的参数名一致
                maxFilesize: 1024*8, // 文件大小限制,单位MB
                maxFiles: 10, // 最大上传文件数
                autoProcessQueue: true, // 自动处理上传队列
                uploadMultiple: true, // 批量发送多个文件
                parallelUploads: 5, // 并行上传数
                acceptedFiles: "*/*", // 接受所有文件类型
                dictDefaultMessage: "", // 清空默认消息,使用自定义消息
                dictFallbackMessage: "您的浏览器不支持拖放文件上传功能。",
                dictFileTooBig: "文件过大 ({{filesize}}MB)。最大文件大小: {{maxFilesize}}MB。",
                dictInvalidFileType: "您不能上传这种类型的文件。",
                dictResponseError: "服务器响应错误 ({{statusCode}})。",
                dictCancelUpload: "取消上传",
                dictCancelUploadConfirmation: "确定要取消上传吗?",
                dictRemoveFile: "移除文件",
                dictRemoveFileConfirmation: null,
                dictMaxFilesExceeded: "您最多只能上传 {{maxFiles}} 个文件。",
                
                // 自定义样式
                previewTemplate: `
                    <div class="dz-preview dz-file-preview">
                        <div class="dz-details">
                            <div class="dz-filename"><span data-dz-name></span></div>
                            <div class="dz-size" data-dz-size></div>
                        </div>
                        <div class="dz-progress" style="display: block;"><span class="dz-upload" data-dz-uploadprogress></span></div>
                        <div class="dz-success-mark"><i class="fa fa-check"></i></div>
                        <div class="dz-error-mark"><i class="fa fa-times"></i></div>
                        <div class="dz-error-message"><span data-dz-errormessage></span></div>
                    </div>
                `,
                
                // 初始化完成回调
                init: function() {
                    console.log("Dropzone初始化完成");
                    
                    // 监听文件添加事件
                    this.on("addedfile", function(file) {
                        console.log("添加文件:", file.name);
                    });
                    
                    // 监听队列完成事件
                    this.on("queuecomplete", function() {
                        console.log("队列上传完成");
                        window.location.href = "{{ url_for('upload_success') }}";
                    });
                },
                
                // 上传成功后的回调
                success: function(file, response) {
                    console.log("上传成功:", file.name);
                    console.log("服务器响应:", response);
                },
                
                // 上传完成后的回调
                complete: function(file) {
                    console.log("上传完成:", file.name);
                },
                
                // 上传错误的回调
                error: function(file, errorMessage) {
                    console.error("上传错误:", errorMessage);
                },
                
                // 上传进度回调
                uploading: function(file, progress, bytesSent) {
                    console.log("上传中:", file.name, "进度:", progress + "%");
                }
            };
            
            console.log("Dropzone配置完成");
        });
    </script>
</body>
</html>

app.py

后端,可以同时接受多个文件。

python 复制代码
from flask import Flask, render_template, request, send_from_directory, abort, redirect, url_for, session
import os
import secrets
import time
import functools

app = Flask(__name__)
app.secret_key = secrets.token_hex(16)  # 用于加密session

# 定义访问密钥
ACCESS_KEY = "12345678"  # 可以根据实际需求修改

# token有效期(秒)
TOKEN_EXPIRY = 3600  # 1小时


def getfile():
    print(os.getcwd())
    path1 = os.getcwd() + '/upload_files'
    f_list = []
    # 递归遍历所有子文件夹,获取所有文件
    for root, dirs, files in os.walk(path1):
        for file in files:
            f_list.append(file)
    print(os.getcwd())
    print(f"获取到的文件列表: {f_list}")
    return f_list


def is_Have_file(filename):
    print(os.getcwd())
    path1 = os.getcwd() + '/upload_files'
    # 递归遍历所有子文件夹,检查文件是否存在
    for root, dirs, files in os.walk(path1):
        if filename in files:
            print(f"文件{filename}存在于{root}目录中")
            return True
    print(f"文件{filename}不存在")
    return False


# 登录装饰器,用于检查用户是否已登录
def login_required(f):
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
        # 检查session中是否有token和token过期时间
        if 'token' not in session or 'token_expiry' not in session:
            return redirect(url_for('login'))
        
        # 检查token是否过期
        if time.time() > session['token_expiry']:
            # token过期,清除session并跳转到登录页
            session.pop('token', None)
            session.pop('token_expiry', None)
            return redirect(url_for('login'))
        
        return f(*args, **kwargs)
    return decorated_function


# 登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # 获取用户输入的密钥
        key = request.form.get('key')
        
        # 验证密钥
        if key == ACCESS_KEY:
            # 生成token并保存到session
            session['token'] = secrets.token_hex(32)
            session['token_expiry'] = time.time() + TOKEN_EXPIRY
            
            # 重定向到首页
            return redirect(url_for('home'))
        else:
            # 密钥错误,显示错误信息
            return render_template('login.html', error='密钥错误,请重新输入')
    
    # GET请求,显示登录页
    return render_template('login.html')


# 登出路由
@app.route('/logout')
def logout():
    # 清除session中的token信息
    session.pop('token', None)
    session.pop('token_expiry', None)
    return redirect(url_for('login'))


@app.route('/')
@login_required
def home():
    return render_template('home.html')


@app.route('/upload')
@login_required
def upload_file():
    return render_template('upload.html')


@app.route('/uploader', methods=['GET', 'POST'])
@login_required
def upload_success():
    if request.method == 'POST':
        print("接收到POST请求")
        print("request.files:", request.files)
        # 处理多文件上传
        files = []
        if 'file' in request.files:
            print("'file'在request.files中")
            files = request.files.getlist('file')  # 获取多个文件
        elif 'file[]' in request.files:
            print("'file[]'在request.files中")
            files = request.files.getlist('file[]')  # 获取多个文件(Dropzone.js默认参数名)
        
        if files:
            print("获取到的文件数量:", len(files))
            uploaded_files = []
            for i, file in enumerate(files):
                print(f"文件{i}: {file.filename}")
                if file.filename:
                    try:
                        # 确保upload_files目录存在
                        if not os.path.exists('upload_files'):
                            os.makedirs('upload_files')
                            print("创建了upload_files目录")
                        # 获取文件扩展名
                        file_extension = os.path.splitext(file.filename)[1].lower().lstrip('.')
                        # 如果没有扩展名,使用'other'文件夹
                        if not file_extension:
                            file_extension = 'other'
                        # 创建对应扩展名的文件夹
                        extension_folder = os.path.join('upload_files', file_extension)
                        if not os.path.exists(extension_folder):
                            os.makedirs(extension_folder)
                            print(f"创建了{file_extension}文件夹")
                        # 保存文件到对应扩展名的文件夹
                        file_path = os.path.join(extension_folder, file.filename)
                        file.save(file_path)
                        print(f"文件保存成功: {file_path}")
                        uploaded_files.append(file.filename)
                    except Exception as e:
                        print(f"保存文件时出错: {str(e)}")
            print(f"成功上传的文件: {uploaded_files}")
            return render_template('upload_success.html', filename=uploaded_files)
        else:
            print("'file'不在request.files中")
    # 处理GET请求
    print("处理GET请求")
    return render_template('upload_success.html')


# 显示下载文件的界面
@app.route('/down', methods=['GET'])
@login_required
def download_page():
    # 获取当前目录参数,默认为空(表示根目录upload_files)
    current_dir = request.args.get('dir', '')
    # 构建完整的目录路径
    base_dir = 'upload_files'
    full_path = os.path.join(base_dir, current_dir)
    
    # 检查路径是否存在且是目录
    if not os.path.exists(full_path) or not os.path.isdir(full_path):
        # 如果路径不存在,返回根目录内容
        current_dir = ''
        full_path = base_dir
    
    # 获取当前目录下的文件夹和文件
    folders = []
    files = []
    if os.path.exists(full_path):
        for item in os.listdir(full_path):
            item_path = os.path.join(full_path, item)
            if os.path.isdir(item_path):
                folders.append(item)
            else:
                files.append(item)
    
    # 构建面包屑导航
    breadcrumbs = []
    if current_dir:
        # 分割当前目录路径(同时处理/和\作为分隔符)
        path_parts = current_dir.replace('/', '\\').split('\\')
        # 移除空部分
        path_parts = [part for part in path_parts if part]
        # 构建每个层级的面包屑
        for i, part in enumerate(path_parts):
            # 构建到当前层级的路径
            crumb_path = '\\'.join(path_parts[:i+1])
            breadcrumbs.append({'name': part, 'path': crumb_path})
    
    print(f"当前目录: {current_dir}")
    print(f"完整路径: {full_path}")
    print(f"文件夹: {folders}")
    print(f"文件: {files}")
    
    return render_template('download_page.html', 
                          folders=folders, 
                          files=files, 
                          current_dir=current_dir, 
                          breadcrumbs=breadcrumbs)


# 下载要下载的文件,要下载的文件是通过get方法来传递的
@app.route('/download_file', methods=['GET'])
@login_required
def download_file():
    if request.method == 'GET':
        download_filename = request.args.get('filename')
        current_dir = request.args.get('dir', '')
        
        # 构建文件路径
        base_dir = 'upload_files'
        if current_dir:
            file_path = os.path.join(base_dir, current_dir, download_filename)
        else:
            file_path = os.path.join(base_dir, download_filename)
        
        # 检查文件是否存在
        if os.path.exists(file_path) and os.path.isfile(file_path):
            # 确定文件所在的目录
            file_dir = os.path.dirname(file_path)
            # 从文件所在的目录发送文件
            return send_from_directory(file_dir, download_filename, as_attachment=True)
        else:
            # 如果直接路径找不到,尝试在所有子目录中查找
            path1 = os.getcwd() + '/upload_files'
            for root, dirs, files in os.walk(path1):
                if download_filename in files:
                    # 从文件所在的目录发送文件
                    return send_from_directory(root, download_filename, as_attachment=True)
            # 如果找不到文件
            abort(404)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
相关推荐
茉莉玫瑰花茶3 小时前
工作流的常见模式 [ 1 ]
java·服务器·前端
南京码讯光电技术有限公司6 小时前
工业无线AP选型指南:从WiFi 5到WiFi 6+5G CPE,如何构建全覆盖、零漫游、高可靠的智能工厂网络?
服务器·网络·5g
二宝哥7 小时前
Linux虚拟机网络配置
linux·运维·服务器
陳10307 小时前
Linux:进程间通信 和 简单进程池
linux·运维·服务器
jimy17 小时前
改.bashrc,直观地判断本地repo是否有改动
linux·服务器
zt1985q7 小时前
本地部署网页监控工具 Webmonitor 并实现外部访问
运维·服务器·网络·网络协议
匆匆那年9678 小时前
远程 Linux 校园网认证操作手册(本地浏览器法)
linux·运维·服务器
dog2509 小时前
为何新增网络路径反而引入额外时延
服务器·网络·php
newnazi9 小时前
RedHat10 安装MS SQL Server2025
linux·服务器·数据库
QuestLab9 小时前
③-进阶篇:vLLM实战——多卡部署、压测与排障
linux·服务器·网络