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)
相关推荐
m0_737302582 小时前
四大厂商云服务器安全创新对比,筑牢数字化转型安全底座
服务器
kyle-fang2 小时前
阿里云服务器部署MySQL
服务器·mysql·阿里云
开开心心_Every2 小时前
Win10/Win11版本一键切换工具
linux·运维·服务器·edge·pdf·web3·共识算法
怣502 小时前
Linux创意命令组合:让终端变得有趣又高效
linux·运维·服务器
lisanmengmeng2 小时前
添加ceph节点
linux·服务器·ceph
Tinyundg3 小时前
Linux系统分区
linux·运维·服务器
要做一个小太阳3 小时前
华为Atlas 900 A3 SuperPoD 超节点网络架构
运维·服务器·网络·华为·架构
UP_Continue3 小时前
Linux--基础IO
linux·运维·服务器
FJW0208143 小时前
使用HAProxy实现动静分离
linux·服务器