1、初始化环境

mkdir webFileManager
cd webFileManager
uv init
uv venv
.venv\Scripts\activate


2、安装依赖

uv pip install flask httpx python-multipart


3、开始编码:

from flask import Flask, jsonify, request, send_from_directory, abort
import os
from datetime import datetime
from pathlib import Path, PurePosixPath

app = Flask(__name__)

BASE_DIR = Path("./kb").resolve()  # 确保此目录存在!

def validate_path(path):
    try:
        # 处理空路径请求(对应根目录)
        if not path.strip():  # 处理空字符串或纯空格
            return str(BASE_DIR)
            
        # 转换并清理路径
        posix_path = PurePosixPath(path)
        fs_path = Path(posix_path)
        
        # 移除所有开头的斜杠和反斜杠(跨平台安全)
        clean_path_str = str(fs_path).lstrip('/\\')
        clean_path = Path(clean_path_str)
        
        # 拼接并解析绝对路径
        target_path = (BASE_DIR / clean_path).resolve()
        
        # 严格验证路径包含关系
        if BASE_DIR not in target_path.parents and target_path != BASE_DIR:
            abort(404)
            
        return str(target_path)
    except (FileNotFoundError, PermissionError, ValueError):
        abort(404)


@app.route('/')
def index():
    return send_from_directory('static', 'index.html')

@app.route('/list')
def list_dir():
    path = request.args.get('path', '')
    full_path = validate_path(path)
    
    if not os.path.exists(full_path):
        return jsonify({"error": "Path not found"}), 404
    
    contents = []
    for item in os.listdir(full_path):
        item_path = os.path.join(full_path, item)
        stat = os.stat(item_path)
        contents.append({
            "name": item,
            "is_dir": os.path.isdir(item_path),
            "size": stat.st_size,
            "modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
        })
    
    return jsonify({
        "path": path,
        "parent": os.path.dirname(path),
        "contents": contents
    })

@app.route('/download/<path:filename>')
def download_file(filename):
    full_path = validate_path(filename)
    if os.path.isfile(full_path):
        return send_from_directory(
            os.path.dirname(full_path),
            os.path.basename(full_path),
            as_attachment=True
        )
    abort(404)

if __name__ == '__main__':
    app.run(debug=True)

后端

然后是前端

<!DOCTYPE html>
<html>
<head>
    <title>知识库管理</title>
    <style>
        body { font-family: "微软雅黑", Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .file-list { list-style: none; padding: 0; }
        .item { padding: 10px; border-bottom: 1px solid #eee; display: flex; align-items: center; }
        .item:hover { background-color: #f5f5f5; }
        .icon { width: 30px; margin-right: 10px; }
        .dir { color: #2196F3; cursor: pointer; }
        .file { color: #4CAF50; }
        .details { margin-left: auto; font-size: 0.9em; color: #666; }
    </style>
</head>
<body>
    <h1>知识库管理</h1>
    <div id="path"></div>
    <ul class="file-list" id="file-list"></ul>

    <script>
        let currentPath = '';
        
        function formatSize(bytes) {
            if (bytes === 0) return '0 B';
            const units = ['B', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(1024));
            return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
        }

        function loadDirectory(path) {
            fetch(`/list?path=${encodeURIComponent(path)}`)
                .then(response => response.json())
                .then(data => {
                    currentPath = data.path;
                    document.getElementById('path').textContent = `当前路径:${currentPath || '/'}`;
                    
                    const list = document.getElementById('file-list');
                    list.innerHTML = '';
                    
                    // 添加上级目录按钮
                    if (data.parent !== '') {
                        const parentItem = document.createElement('li');
                        parentItem.className = 'item dir';
                        parentItem.innerHTML = `
                            <span class="icon">📁</span>
                            <span>.. (上一级目录)</span>
                        `;
                        parentItem.onclick = () => loadDirectory(data.parent);
                        list.appendChild(parentItem);
                    }

                    // 新增排序逻辑
                    const sortedContents = data.contents.sort((a, b) => {
                        // 目录优先
                        if (a.is_dir && !b.is_dir) return -1;
                        if (!a.is_dir && b.is_dir) return 1;
                        // 按名称排序
                        return a.name.localeCompare(b.name, 'zh-Hans-CN', { sensitivity: 'accent' });
                    });

                    sortedContents.forEach(item => {
                        const li = document.createElement('li');
                        li.className = `item ${item.is_dir ? 'dir' : 'file'}`;
                        
                        const details = `<div class="details">
                            ${formatSize(item.size)} | 
                            ${new Date(item.modified).toLocaleString()}
                        </div>`;
                        
                        li.innerHTML = `
                            <span class="icon">${item.is_dir ? '📁' : '📄'}</span>
                            <span>${item.name}</span>
                            ${details}
                        `;

                        if (item.is_dir) {
                            li.onclick = () => loadDirectory(`${currentPath}/${item.name}`.replace('//', '/'));
                        } else {
                            li.style.cursor = 'pointer';
                            li.onclick = () => window.location.href = `/download/${encodeURIComponent(`${currentPath}/${item.name}`)}`;
                        }

                        list.appendChild(li);
                    });
                });
        }

        // 初始化加载根目录
        loadDirectory('');
    </script>
</body>
</html>