一个基于HTML/php的python工具管理平台

     一个用于存放 Python 代码的平台,具有前后端页面。可设置和展示多个项目(pyhton制作的小工具),每个项目都可以设置、上传:代码、界面截图、exe程序,还可以设置一句话简介和详细介绍(支持Makedown)。

截图

前端

后端

文件结构

项目文件结构概览

/ (网站根目录)
├── index.php                 # 公共展示页(用户访问的下载页面)
├── login.php                 # 后台登录页面
├── admin/                    # 后台管理目录
│   ├── index.php             # 后台逻辑核心 (处理 POST、文件分拣、版本排序)
│   └── template.html         # 后台 UI 模板 (包含全局粘贴、拖拽、图片预览 JS)
├── projects/                 # 项目数据根目录 (需 775/777 权限)
│   ├── ProjectName_A/        # 具体项目文件夹 (名称可由后台修改)
│   │   ├── emoji.txt         # 存储项目图标 (如: 🚀)
│   │   ├── info.txt          # 项目详细 Markdown 介绍
│   │   ├── summary.txt       # 项目一句话简介
│   │   ├── requirements.txt  # 环境依赖说明
│   │   ├── v2.0/             # 版本文件夹 (高版本)
│   │   │   ├── screenshots/  # 该版本的截图目录 (.png, .jpg)
│   │   │   ├── exe/          # 该版本的执行文件目录 (.exe)
│   │   │   └── Project_v2.0.py # 该版本的源码文件 (.py 或 .pyw)
│   │   └── v1.0/             # 历史版本文件夹
│   │       ├── screenshots/
│   │       ├── exe/
│   │       └── Project_v1.0.py
│   └── ProjectName_B/        # 另一个项目
└── assets/                   # (可选) 公共静态资源 (CSS/JS 库)

关键目录与文件说明

1. admin/ 目录

  • index.php: 扮演了“控制器”的角色。它负责扫描 projects/ 目录下的文件夹并转换为项目列表,同时处理所有的上传逻辑。

  • template.html: 扮演了“视图”的角色。通过内嵌 JavaScript 监听全局 paste 事件实现 Ctrl+V 上传,并利用 FileReader API 实现 .py 文件的即时读取与代码区自动填充。

2. projects/ 目录 (数据层)

这是系统的“数据库”。

  • 分层设计: 采用 项目 -> 版本 -> 附件 的三级结构。

  • 自动分拣: 后端逻辑会根据文件后缀,自动将上传的文件放入对应的子目录:

    • 图片 $\rightarrow$ /screenshots/

    • 可执行程序 $\rightarrow$ /exe/

    • 源码 $\rightarrow$ 根目录

3. 命名约定

  • 源码文件: 采用 项目名_版本号.py 的格式自动命名。

  • 重命名逻辑: 当你在后台修改项目名称时,admin/index.php 会调用 rename() 函数更改对应的文件夹名称,由于所有路径都是动态生成的,修改后前台页面会自动同步。


部署建议

  1. 权限设置: 确保 projects/ 文件夹对 Web 服务器(如 www-dataapache 用户)具有写权限

  2. 安全防护: projects/ 目录内可以放置一个简单的 .htaccess (如果你使用 Apache) 来禁止执行 PHP 脚本,防止恶意上传。

代码

前端

index.php:

index.php
<?php
// 根目录 index.php - 用户展示页

$projectsDir = __DIR__ . '/projects/';
$projectListForSidebar = [];

if (is_dir($projectsDir)) {
    $dirs = array_filter(glob($projectsDir . '*'), 'is_dir');
    foreach ($dirs as $dir) {
        $name = basename($dir);
        $emojiFile = $dir . '/emoji.txt';
        $emoji = file_exists($emojiFile) ? file_get_contents($emojiFile) : '🚀';
        
        $projectListForSidebar[] = [
            'name' => $name,
            'emoji' => $emoji
        ];
    }
    usort($projectListForSidebar, function($a, $b) {
        return strcmp($a['name'], $b['name']);
    });
}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8" />
    <title>Python工具列表</title>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
    
    <style>
        :root { --sidebar-w: 300px; --primary: #3498db; --dark: #2c3e50; }
        body { margin:0; font-family: "Segoe UI", Tahoma, sans-serif; display: flex; height: 100vh; background-color: #f4f7f6; overflow: hidden; }
        
        /* 侧边栏 */
        #sidebar { width: var(--sidebar-w); background: var(--dark); color: #ecf0f1; overflow-y: auto; flex-shrink: 0; }
        #sidebar h2 { text-align: center; padding: 25px 0; margin: 0; background: #1a252f; font-size: 1.3em; }
        #projectList { list-style: none; padding: 0; margin: 0; }
        #projectList li { padding: 15px 20px; cursor: pointer; border-bottom: 1px solid #34495e; display: flex; align-items: center; transition: 0.2s; }
        #projectList li:hover { background: #34495e; }
        #projectList li.active { background: var(--primary); color: white; }
        .proj-emoji-icon { font-size: 22px; margin-right: 12px; width: 25px; text-align: center; }

        /* 主内容 */
        #main { flex: 1; padding: 30px 50px; overflow-y: auto; background-color: #fff; }
        .project-header { display: flex; align-items: center; gap: 20px; margin-bottom: 30px; border-bottom: 2px solid #eee; padding-bottom: 25px; }
        .header-emoji { font-size: 50px; }
        .header-text h1 { margin: 0; color: var(--dark); }
        .header-text .summary { color: #7f8c8d; margin-top: 5px; }

        /* 版本切换器样式 */
        .version-selector { margin-bottom: 20px; display: flex; align-items: center; gap: 10px; background: #f8f9fa; padding: 10px 15px; border-radius: 6px; }
        #versionSelect { padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd; outline: none; }

        .section { margin-bottom: 40px; }
        .section h3 { color: var(--dark); border-left: 4px solid var(--primary); padding-left: 12px; margin-bottom: 15px; }
        
        /* 代码框高度受限 */
        /* 1. 限制宽度并让右侧留白 */
        .code-block-wrapper { 
            background: #282c34; 
            border-radius: 8px; 
            overflow: hidden;
            border: 1px solid #3e4451; 
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
            
            /* 修改这里:设置最大宽度,例如 80% 或固定像素 */
            max-width: 85%; 
            margin-right: auto; /* 确保左对齐,右侧自然留白 */
        }
        
        /* 2. 进一步降低高度 */
        .code-container { 
            max-height: 300px; /* 原来是 500px,改为 350px 甚至更低 */
            overflow-y: auto; 
            overflow-x: auto; /* 确保横向太长时也可以滚动 */
        }

        pre { margin: 0; padding: 15px; font-family: 'Cascadia Code', Consolas, monospace; font-size: 14px; }

        .screenshots-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; }
        .screenshots-grid img { width: 100%; border-radius: 6px; cursor: pointer; transition: 0.2s; border: 1px solid #eee; }
        .screenshots-grid img:hover { transform: scale(1.03); }

        .file-link { 
            display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px; 
            background: #27ae60; color: white; text-decoration: none; border-radius: 4px; font-size: 14px; font-weight: 500;
        }
        .file-link.source { background: #34495e; }

        /* 1. 确保模态框背景层开启 Flex 居中 */
        .modal { 
            display: none; 
            position: fixed; 
            z-index: 1000; 
            left: 0; 
            top: 0; 
            width: 100%; 
            height: 100%; 
            background: rgba(0,0,0,0.9); 
            
            /* 核心修改:确保水平和垂直全部居中 */
            justify-content: center; 
            align-items: center; 
        }
        
        /* 2. 优化图片显示 */
        .modal-content { 
            max-width: 90%; 
            max-height: 90%; 
            object-fit: contain; 
            /* 移除可能存在的 margin 干扰 */
            margin: auto; 
            display: block; 
        }
        
                /* 介绍按钮样式 */
        .intro-trigger-btn {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 12px 20px;
            background: #f8f9fa;
            border: 1px dashed #3498db;
            color: #3498db;
            border-radius: 8px;
            cursor: pointer;
            transition: 0.3s;
            font-weight: bold;
        }
        .intro-trigger-btn:hover {
            background: #eaf2f8;
            border-style: solid;
        }
        
        /* 详细介绍弹出层 (专用) */
        #infoModal {
            display: none;
            position: fixed;
            z-index: 1001; /* 比图片弹出层更高 */
            left: 0; top: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.6);
            justify-content: center;
            align-items: center;
        }
        
        .info-modal-content {
            background: white;
            width: 80%;
            max-width: 900px;
            height: 80vh;
            border-radius: 12px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            animation: modalFadeIn 0.3s ease;
        }
        
        @keyframes modalFadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        .info-modal-header {
            padding: 15px 25px;
            background: #f8f9fa;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .info-modal-body {
            flex: 1;
            padding: 30px;
            overflow-y: auto;
            line-height: 1.8;
        }
        
        .close-info-btn {
            font-size: 24px;
            cursor: pointer;
            color: #999;
        }
        
    </style>
</head>
<body>

<div id="sidebar">
    <h2>🛠️Python工具列表</h2>
    <ul id="projectList">
        <?php foreach ($projectListForSidebar as $p): ?>
            <li onclick="loadProject('<?php echo $p['name']; ?>', this)">
                <span class="proj-emoji-icon"><?php echo htmlspecialchars($p['emoji']); ?></span>
                <span><?php echo htmlspecialchars(str_replace('_', ' ', $p['name'])); ?></span>
            </li>
        <?php endforeach; ?>
    </ul>
</div>

<div id="main">
    <div id="welcome" style="text-align: center; padding-top: 20vh; color: #bdc3c7;">
        <h1>👈 请选择一个项目</h1>
    </div>

    <div id="projectDetail" style="display: none;">
        <div class="project-header">
            <div id="displayEmoji" class="header-emoji"></div>
            <div class="header-text">
                <h1 id="displayTitle"></h1>
                <div id="displaySummary" class="summary"></div>
            </div>
        </div>

        <div class="section">
            <h3>项目介绍</h3>
            <div id="introShort">
                <div class="intro-trigger-btn" onclick="openInfoModal()">
                    📖 点击查看详细项目介绍 (Markdown)
                </div>
            </div>
        </div>
        
        <div class="section" id="reqSection" style="display:none; margin-top: 20px;">
            <h3>环境依赖</h3>
            <div id="displayReq" style="background: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; border-radius: 8px; font-family: monospace; white-space: pre-wrap; line-height: 1.5; color: #555;">
                </div>
        </div>
        
        <div id="infoModal" onclick="closeInfoModal(event)">
            <div class="info-modal-content" onclick="event.stopPropagation()">
                <div class="info-modal-header">
                    <h3 style="margin:0; border:none; padding:0;">详细介绍</h3>
                    <span class="close-info-btn" onclick="closeInfoModal()">&times;</span>
                </div>
                <div id="displayInfo" class="info-modal-body markdown-body">
                    </div>
            </div>
        </div>

        <div class="version-selector" id="verSelector" style="display:none;">
            <span>选择版本:</span>
            <select id="versionSelect" onchange="changeVersion()"></select>
        </div>



        <div class="section" id="codeSection">
            <h3>Python 源码</h3>
            <div class="code-block-wrapper">
                <div class="code-header">
                    <span class="code-filename" id="displayFilename"></span>
                    <button onclick="copyCode()" style="font-size:11px; cursor:pointer;">复制</button>
                </div>
                <div class="code-container">
                    <pre><code id="displayCode" class="language-python"></code></pre>
                </div>
            </div>
            <div id="sourceDL" style="margin-top:10px;"></div>
        </div>

        <div class="section" id="ssSection">
            <h3>界面截图</h3>
            <div class="screenshots-grid" id="displaySS"></div>
        </div>

        <div class="section" id="dlSection">
            <h3>exe下载</h3>
            <div id="displayFiles" style="display:flex; gap:10px; flex-wrap:wrap;"></div>
        </div>
    </div>
</div>

<div id="imageModal" class="modal" onclick="this.style.display='none'">
    <img class="modal-content" id="modalImage">
</div>

<script>
    let currentProjectName = "";
    let allVersionData = {}; // 存储当前项目的所有版本数据

    // 1. 加载项目(获取版本列表)
    async function loadProject(name, el) {
        document.querySelectorAll('#projectList li').forEach(li => li.classList.remove('active'));
        el.classList.add('active');
        currentProjectName = name;

        const resp = await fetch(`get_details.php?project=${encodeURIComponent(name)}&all_versions=1`);
        const data = await resp.json();
        
        // 渲染运行环境依赖
        const reqSection = document.getElementById('reqSection');
        const reqDisplay = document.getElementById('displayReq');
        
        if (reqSection && reqDisplay) {
            if (data.requirements && data.requirements.trim() !== "") {
                reqDisplay.innerText = data.requirements; // 填入文字
                reqSection.style.display = 'block';       // 显示区域
            } else {
                reqSection.style.display = 'none';        // 没内容则隐藏
            }
        }
        
        allVersionData = data; // 假设后端返回了所有版本
        
        // 渲染基本信息
        document.getElementById('welcome').style.display = 'none';
        document.getElementById('projectDetail').style.display = 'block';
        document.getElementById('displayTitle').innerText = name.replace(/_/g, ' ');
        document.getElementById('displayEmoji').innerText = data.emoji || '🚀';
        document.getElementById('displaySummary').innerText = data.summary || '';
        document.getElementById('displayInfo').innerHTML = marked.parse(data.info || '');

        // 处理版本下拉框
        const select = document.getElementById('versionSelect');
        select.innerHTML = '';
        if(data.available_versions && data.available_versions.length > 0) {
            data.available_versions.forEach(v => {
                const opt = document.createElement('option');
                opt.value = v;
                opt.innerText = 'v' + v;
                if(v === data.version) opt.selected = true;
                select.appendChild(opt);
            });
            document.getElementById('verSelector').style.display = 'flex';
        }

        renderVersionDetails(data);
    }

    // 2. 切换版本
    async function changeVersion() {
        const v = document.getElementById('versionSelect').value;
        const resp = await fetch(`get_details.php?project=${encodeURIComponent(currentProjectName)}&version=${v}`);
        const data = await resp.json();
        renderVersionDetails(data);
    }

    // 3. 渲染特定版本的详情
    function renderVersionDetails(data) {
        // 渲染代码
        if (data.code) {
            document.getElementById('codeSection').style.display = 'block';
            document.getElementById('displayFilename').innerText = data.code.filename;
            document.getElementById('displayCode').textContent = data.code.content;
            document.getElementById('sourceDL').innerHTML = `<a href="projects/${currentProjectName}/${data.version}/${data.code.filename}" class="file-link source" download>💾 下载源码</a>`;
            hljs.highlightAll();
        } else {
            document.getElementById('codeSection').style.display = 'none';
        }

        // 渲染截图
        const ssGrid = document.getElementById('displaySS');
        ssGrid.innerHTML = '';
        if (data.screenshots && data.screenshots.length > 0) {
            data.screenshots.forEach(s => {
                const img = document.createElement('img');
                img.src = `projects/${currentProjectName}/${data.version}/screenshots/${s}`;
                img.onclick = () => {
                    document.getElementById('imageModal').style.display = 'flex';
                    document.getElementById('modalImage').src = img.src;
                };
                ssGrid.appendChild(img);
            });
            document.getElementById('ssSection').style.display = 'block';
        } else {
            document.getElementById('ssSection').style.display = 'none';
        }

        // 渲染下载
        const dlBox = document.getElementById('displayFiles');
        dlBox.innerHTML = '';
        if (data.exes && data.exes.length > 0) {
            data.exes.forEach(exe => {
                dlBox.innerHTML += `<a href="projects/${currentProjectName}/${data.version}/exe/${exe}" class="file-link" download>📦 下载 ${exe}</a>`;
            });
            document.getElementById('dlSection').style.display = 'block';
        } else {
            document.getElementById('dlSection').style.display = 'none';
        }
    }

    function copyCode() {
        const code = document.getElementById('displayCode').innerText;
        navigator.clipboard.writeText(code);
        alert('代码已复制到剪贴板');
    }
    
        // --- 弹出详细介绍 ---
    function openInfoModal() {
        const modal = document.getElementById('infoModal');
        if (modal) {
            modal.style.display = 'flex';
            document.body.style.overflow = 'hidden'; 
        }
    }
    
    function closeInfoModal() {
        const modal = document.getElementById('infoModal');
        if (modal) {
            modal.style.display = 'none';
            document.body.style.overflow = '';
        }
    }
    
    
</script>
</body>
</html>

script.js:

script.js
// script.js

// 你的后台 projects 目录,路径根据你网站部署调整
const projectsBaseURL = '/projects'; // 这是前端用于构建文件下载/显示URL的基路径

// 页面元素
const projectListElem = document.getElementById('projectList');
const projectTitleElem = document.getElementById('projectTitle');

// 选项卡内容元素
const screenshotsTabContent = document.getElementById('screenshots-tab');
const codeTabContent = document.getElementById('code-tab');
const downloadTabContent = document.getElementById('download-tab');
const infoTabContent = document.getElementById('info-tab');

// 选项卡按钮
const tabButtons = document.querySelectorAll('.tab-button');

let projects = [];
let currentProject = null;

// 获取项目列表(读取项目根目录,列文件夹名)
async function fetchProjects() {
  try {
    const res = await fetch('/api/projects_list.php'); 
    if (!res.ok) throw new Error('无法获取项目列表');
    projects = await res.json();
    renderProjectList();
  } catch(e) {
    projectListElem.innerHTML = '<li>无法加载项目列表</li>';
    console.error('Error fetching project list:', e);
  }
}

// 渲染项目列表,取消显示图标
function renderProjectList() {
    projectListElem.innerHTML = '';
    projects.forEach(p => {
        const li = document.createElement('li');
        // 直接添加项目名称文本,不显示图标
        li.textContent = p; 
        li.dataset.proj = p;
        li.onclick = () => {
            if(currentProject !== p) {
                currentProject = p;
                setActiveProject(p);
                loadProjectData(p);
            }
        };
        projectListElem.appendChild(li);
    });
}

function setActiveProject(p) {
  [...projectListElem.children].forEach(li=>{
    li.classList.toggle('active', li.dataset.proj === p);
  });
}

// 选项卡切换逻辑
function showTab(tabId) {
    // 移除所有按钮的 active 类
    tabButtons.forEach(button => button.classList.remove('active'));
    // 隐藏所有内容
    document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));

    // 激活点击的按钮
    document.querySelector(`.tab-button[data-tab="${tabId}"]`).classList.add('active');
    // 显示对应的内容
    document.getElementById(`${tabId}-tab`).classList.add('active');
}

// 为选项卡按钮添加点击事件监听器
tabButtons.forEach(button => {
    button.addEventListener('click', () => {
        showTab(button.dataset.tab);
    });
});


async function loadProjectData(proj) {
    projectTitleElem.textContent = proj;

    // 清空所有选项卡内容
    screenshotsTabContent.innerHTML = '<p>加载中...</p>';
    codeTabContent.innerHTML = '<p>加载中...</p>';
    downloadTabContent.innerHTML = '<p>加载中...</p>';
    infoTabContent.innerHTML = '<p>加载中...</p>';

    // 默认显示截图选项卡
    showTab('screenshots');

    // 加载并显示截图
    try {
        const res = await fetch(`/api/project_files.php?project=${encodeURIComponent(proj)}&type=screenshots`);
        if(!res.ok) throw new Error('无法获取截图列表');
        const screenshots = await res.json();
        renderScreenshots(screenshots, proj);
    } catch(e) {
        screenshotsTabContent.innerHTML = '<p>无截图</p>';
        console.error('Error fetching screenshots:', e);
    }

    // 加载并显示 Python 代码文件(渲染到代码选项卡和下载选项卡)
    let codeFilesData = [];
    try {
        const res = await fetch(`/api/project_files.php?project=${encodeURIComponent(proj)}&type=code`);
        if(!res.ok) throw new Error('无法获取代码文件列表');
        codeFilesData = await res.json();
        renderCodeFiles(codeFilesData, proj); // 渲染到代码选项卡
    } catch(e) {
        codeTabContent.innerHTML = '<p>无代码文件</p>';
        console.error('Error fetching code files:', e);
    }

    // 加载并显示 EXE 程序文件(渲染到下载选项卡)
    let exeFiles = [];
    try {
        const res = await fetch(`/api/project_files.php?project=${encodeURIComponent(proj)}&type=exe`);
        if(!res.ok) throw new Error('无法获取EXE文件列表');
        exeFiles = await res.json();
        // renderExeFiles(exeFiles, proj); // 不再单独渲染EXE,而是合并到下载选项卡
    } catch(e) {
        // exeFilesElem.innerHTML = '<p>无exe文件</p>'; // 这个不再需要
        console.error('Error fetching exe files:', e);
    }
    // 合并渲染下载文件,确保所有文件(代码和EXE)都显示
    renderDownloadFiles(codeFilesData, exeFiles, proj);


    // 获取并显示项目介绍 (功能介绍) - Markdown支持
    try {
        const res = await fetch(`${projectsBaseURL}/${encodeURIComponent(proj)}/info.txt`);
        if(res.ok) {
            const markdownText = await res.text();
            infoTabContent.innerHTML = marked.parse(markdownText); // Markdown支持
        } else {
            infoTabContent.innerHTML = '<p>无项目介绍</p>';
        }
    } catch(e) {
        infoTabContent.innerHTML = '<p>无项目介绍</p>';
        console.error('Error fetching project info:', e);
    }
    
    // 重新运行 highlight.js 来高亮新加载的代码块
    // 确保在代码内容加载完成后调用
    setTimeout(() => {
        if (typeof hljs !== 'undefined') {
            hljs.highlightAll();
        }
    }, 100); // 稍微延迟,确保DOM更新完成
}

function renderScreenshots(imgs, proj) {
  screenshotsTabContent.innerHTML = ''; // 清空内容
  if(imgs.length === 0) {
    screenshotsTabContent.innerHTML = '<p>无截图</p>';
    return;
  }
  const container = document.createElement('div');
  container.className = 'screenshots-container'; // 使用新类名
  imgs.forEach(img=>{
    const imgElem = document.createElement('img');
    imgElem.src = `${projectsBaseURL}/${encodeURIComponent(proj)}/screenshots/${encodeURIComponent(img)}`;
    imgElem.alt = img;
    container.appendChild(imgElem);
  });
  screenshotsTabContent.appendChild(container);
}

function renderCodeFiles(filesData, proj) {
    codeTabContent.innerHTML = ''; // 清空内容
    if(filesData.length === 0) {
        codeTabContent.innerHTML = '<p>无代码文件</p>';
        return;
    }
    filesData.forEach(file => {
        const fileBlock = document.createElement('div');
        fileBlock.className = 'code-block-wrapper';
        
        fileBlock.innerHTML = `
            <h4>${htmlspecialchars(file.name)}</h4>
            <pre><code class="language-python">${htmlspecialchars(file.content)}</code></pre>
        `;
        codeTabContent.appendChild(fileBlock);
    });
}

// 渲染下载文件(合并代码和EXE)
function renderDownloadFiles(codeFiles, exeFiles, proj) {
    downloadTabContent.innerHTML = ''; // 清空内容

    let hasDownloads = false;

    if (codeFiles.length > 0) {
        downloadTabContent.innerHTML += '<h4>Python 代码文件下载</h4>';
        const ul = document.createElement('ul');
        ul.style.listStyle = 'none';
        ul.style.paddingLeft = '0';
        codeFiles.forEach(f => {
            const baseUrl = `${projectsBaseURL}/${encodeURIComponent(proj)}/code/${encodeURIComponent(f.name)}`;
            const li = document.createElement('li');
            li.style.marginBottom = '5px';
            li.innerHTML = `
                <a href="${baseUrl}" download class="file-link">${htmlspecialchars(f.name)}</a>
                <span style="font-size:0.9em; color:#666; margin-left:10px;">(点击下载)</span>
            `;
            ul.appendChild(li);
        });
        downloadTabContent.appendChild(ul);
        hasDownloads = true;
    }

    if (exeFiles.length > 0) {
        downloadTabContent.innerHTML += '<h4>EXE 程序文件下载</h4>';
        const ul = document.createElement('ul');
        ul.style.listStyle = 'none';
        ul.style.paddingLeft = '0';
        exeFiles.forEach(f => {
            const baseUrl = `${projectsBaseURL}/${encodeURIComponent(proj)}/exe/${encodeURIComponent(f)}`;
            const li = document.createElement('li');
            li.style.marginBottom = '5px';
            li.innerHTML = `
                <a href="${baseUrl}" download class="file-link">${htmlspecialchars(f)}</a>
                <span style="font-size:0.9em; color:#666; margin-left:10px;">(点击下载)</span>
            `;
            ul.appendChild(li);
        });
        downloadTabContent.appendChild(ul);
        hasDownloads = true;
    }

    if (!hasDownloads) {
        downloadTabContent.innerHTML = '<p>无下载文件。</p>';
    }
}


// 简单的 HTML 转义函数,防止代码内容破坏 HTML 结构
function htmlspecialchars(str) {
    let div = document.createElement('div');
    div.appendChild(document.createTextNode(str));
    return div.innerHTML;
}

// 页面加载时初始化项目列表
fetchProjects();

// 确保 highlight.js 在 DOMContentLoaded 后运行
document.addEventListener('DOMContentLoaded', (event) => {
    // 首次加载页面时,如果没有项目被选中,不立即高亮
    // hljs.highlightAll() 会在 loadProjectData 内部被调用
});

后端

/admin/index.php

/admin/index.php
<?php
// admin/index.php

// 1. 错误报告设置
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

session_start();

// 2. 认证检查
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
    header('Location: login.php');
    exit;
}

// 定义项目数据目录
const PROJECTS_DIR = __DIR__ . '/../projects/';

// 确保项目目录存在
if (!is_dir(PROJECTS_DIR)) {
    mkdir(PROJECTS_DIR, 0775, true);
}

// --- 辅助函数 ---
function generateCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function validateCsrfToken($token) {
    if (empty($token) || empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $token)) {
        unset($_SESSION['csrf_token']);
        return false;
    }
    return true;
}

function validateName($name) {
    return preg_match('/^[\p{L}a-zA-Z0-9_\-\.]+$/u', $name) && mb_strlen($name, 'UTF-8') <= 50;
}

function deleteDir($dir) {
    if (!is_dir($dir)) return false;
    $files = array_diff(scandir($dir), ['.', '..']);
    foreach ($files as $file) {
        (is_dir("$dir/$file")) ? deleteDir("$dir/$file") : unlink("$dir/$file");
    }
    return rmdir($dir);
}

function getProjectPath($projectName) {
    return PROJECTS_DIR . basename($projectName);
}

// --- 数据初始化 ---
$projects = [];
$currentProject = $_GET['project'] ?? null;
$currentVersion = $_GET['version'] ?? null;
$generalInfoText = '';
$summaryText = '';      
$requirementsText = ''; 
$emojiText = '🚀'; 
$projectVersions = [];
$versionInfoText = '';
$codeContent = '';
$screenshots = [];
$exeFiles = []; // 存放带大小信息的数组
$csrfToken = generateCsrfToken();

// 获取项目列表及其 Emoji
$projectListWithEmoji = []; 
if (is_dir(PROJECTS_DIR)) {
    $dirs = array_filter(glob(PROJECTS_DIR . '*'), 'is_dir');
    foreach ($dirs as $dir) {
        $projectName = basename($dir);
        if (validateName($projectName)) {
            $eFile = $dir . '/emoji.txt';
            $emoji = file_exists($eFile) ? file_get_contents($eFile) : '🚀';
            $projectListWithEmoji[] = ['name' => $projectName, 'emoji' => $emoji];
            $projects[] = $projectName; 
        }
    }
    usort($projectListWithEmoji, function($a, $b) { return strcmp($a['name'], $b['name']); });
}

if (!$currentProject && !empty($projects)) {
    $currentProject = $projects[0];
}

// 加载当前项目数据
if ($currentProject && in_array($currentProject, $projects)) {
    $projectPath = getProjectPath($currentProject);
    
    // 加载基础文件
    foreach (['info.txt' => 'generalInfoText', 'summary.txt' => 'summaryText', 'requirements.txt' => 'requirementsText', 'emoji.txt' => 'emojiText'] as $file => $var) {
        $path = $projectPath . '/' . $file;
        if (file_exists($path)) $$var = file_get_contents($path);
    }

    // 获取版本列表
    $versionDirs = array_filter(glob($projectPath . '/*'), 'is_dir');
    foreach ($versionDirs as $vDir) {
        $versionName = basename($vDir);
        if (validateName($versionName)) $projectVersions[] = $versionName;
    }

    // --- 需求2:高版本在前 ---
    usort($projectVersions, function($a, $b) {
        return version_compare($b, $a); 
    });

    if (!$currentVersion && !empty($projectVersions)) {
        $currentVersion = $projectVersions[0]; // 默认选最高版本
    }

    if ($currentVersion && in_array($currentVersion, $projectVersions)) {
        $versionPath = $projectPath . '/' . basename($currentVersion);
        
        // 源码扫描 (支持 .py 和 .pyw)
        $pyFiles = array_merge(glob($versionPath . '/*.py'), glob($versionPath . '/*.pyw'));
        if (!empty($pyFiles)) $codeContent = file_get_contents($pyFiles[0]);

        // 截图扫描
        $screenshotsDir = $versionPath . '/screenshots/';
        if (is_dir($screenshotsDir)) {
            $screenshots = array_diff(scandir($screenshotsDir), ['.', '..']);
            $screenshots = array_values(preg_grep('/\.(png|jpg|jpeg|gif|bmp|webp)$/i', $screenshots));
            sort($screenshots);
        }

        // --- 需求7:EXE 展示文件大小 ---
        $exeDir = $versionPath . '/exe/';
        if (is_dir($exeDir)) {
            $exes = array_diff(scandir($exeDir), ['.', '..']);
            foreach ($exes as $exe) {
                if (strtolower(pathinfo($exe, PATHINFO_EXTENSION)) === 'exe') {
                    $fpath = $exeDir . $exe;
                    $size = file_exists($fpath) ? filesize($fpath) : 0;
                    $sizeStr = ($size > 1048576) ? round($size / 1048576, 2) . ' MB' : round($size / 1024, 2) . ' KB';
                    $exeFiles[] = ['name' => $exe, 'size' => $sizeStr];
                }
            }
        }
    }
}

// --- 处理 POST 请求 ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!validateCsrfToken($_POST['csrf_token'] ?? '')) {
        exit('CSRF Token Invalid');
    }

    // 新建项目
    if (isset($_POST['new_project'])) {
        $newName = str_replace(' ', '_', trim($_POST['project_name'] ?? ''));
        if (validateName($newName) && !is_dir(getProjectPath($newName))) {
            mkdir(getProjectPath($newName), 0775, true);
            header('Location: ?project=' . urlencode($newName));
            exit;
        }
    }

    // 重命名项目
    if (isset($_POST['rename_project'])) {
        $oldName = trim($_POST['old_name'] ?? '');
        $newName = str_replace(' ', '_', trim($_POST['new_name'] ?? ''));
        if (validateName($oldName) && validateName($newName) && $oldName !== $newName) {
            $oldP = getProjectPath($oldName);
            $newP = getProjectPath($newName);
            if (is_dir($oldP) && !is_dir($newP)) {
                rename($oldP, $newP);
                header('Location: ?project=' . urlencode($newName));
                exit;
            }
        }
    }

    // 删除项目
    if (isset($_POST['delete_project'])) {
        $delName = trim($_POST['del_name'] ?? '');
        if (validateName($delName) && is_dir(getProjectPath($delName))) {
            deleteDir(getProjectPath($delName));
            header('Location: index.php');
            exit;
        }
    }

    // 保存信息
    if (isset($_POST['save_general_info'])) {
        $proj = trim($_POST['proj'] ?? '');
        if (in_array($proj, $projects)) {
            $pPath = getProjectPath($proj);
            file_put_contents($pPath . '/info.txt', $_POST['info'] ?? '');
            file_put_contents($pPath . '/summary.txt', $_POST['summary'] ?? '');
            file_put_contents($pPath . '/requirements.txt', $_POST['requirements'] ?? '');
            file_put_contents($pPath . '/emoji.txt', trim($_POST['emoji'] ?? '🚀'));
        }
        header("Location: ?project=" . urlencode($proj) . "&version=" . urlencode($currentVersion));
        exit;
    }

    // 新增版本
    if (isset($_POST['new_version'])) {
        $proj = trim($_POST['proj'] ?? '');
        $vName = trim($_POST['version_name'] ?? '');
        if (validateName($proj) && validateName($vName)) {
            $vPath = getProjectPath($proj) . '/' . $vName;
            if (!is_dir($vPath)) {
                mkdir($vPath, 0775, true);
                mkdir($vPath . '/screenshots', 0775, true);
                mkdir($vPath . '/exe', 0775, true);
                header("Location: ?project=" . urlencode($proj) . "&version=" . urlencode($vName));
                exit;
            }
        }
    }

    // 保存代码
    if (isset($_POST['save_code'])) {
        $proj = trim($_POST['proj'] ?? '');
        $version = trim($_POST['version'] ?? '');
        if (validateName($proj) && validateName($version)) {
            $vPath = getProjectPath($proj) . '/' . $version;
            // 清理旧代码
            array_map('unlink', array_merge(glob("$vPath/*.py"), glob("$vPath/*.pyw")));
            // 默认保存为 .py,除非前端指定了特殊后缀
            $ext = isset($_POST['is_pyw']) ? 'pyw' : 'py';
            file_put_contents("$vPath/{$proj}_{$version}.{$ext}", $_POST['code_content'] ?? '');
        }
        header("Location: ?project=" . urlencode($proj) . "&version=" . urlencode($version));
        exit;
    }

    // 批量上传
    if (isset($_POST['action']) && $_POST['action'] === 'upload_files') {
        $proj = trim($_POST['proj'] ?? '');
        $version = trim($_POST['version'] ?? '');
        if (isset($_FILES['file'])) {
            $base = getProjectPath($proj) . '/' . $version;
            foreach ($_FILES['file']['name'] as $k => $name) {
                if ($_FILES['file']['error'][$k] == 0) {
                    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
                    $targetDir = $base . '/';
                    if (in_array($ext, ['jpg','jpeg','png','gif','bmp','webp'])) $targetDir .= 'screenshots/';
                    elseif ($ext === 'exe') $targetDir .= 'exe/';
                    
                    if (!is_dir($targetDir)) mkdir($targetDir, 0775, true);
                    move_uploaded_file($_FILES['file']['tmp_name'][$k], $targetDir . basename($name));
                }
            }
        }
        header("Location: ?project=" . urlencode($proj) . "&version=" . urlencode($version));
        exit;
    }

    // 删除文件
    if (isset($_POST['delete_file'])) {
        $proj = trim($_POST['proj'] ?? '');
        $version = trim($_POST['version'] ?? '');
        $type = $_POST['type'] ?? ''; 
        $file = $_POST['file'] ?? '';
        $target = getProjectPath($proj) . '/' . $version . '/' . ($type === 'exe' ? 'exe/' : 'screenshots/') . basename($file);
        if (file_exists($target)) unlink($target);
        header("Location: ?project=" . urlencode($proj) . "&version=" . urlencode($version));
        exit;
    }
}

// 退出与消息处理
if (isset($_GET['logout'])) { session_destroy(); header('Location: login.php'); exit; }
$message = $_SESSION['message'] ?? null; unset($_SESSION['message']);

include 'template.html';

admin/template.html:

template.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>项目管理后台</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <style>
        :root { --primary: #3498db; --danger: #e74c3c; --bg: #f4f7f6; }
        body { font-family: 'Segoe UI', Tahoma, sans-serif; background: var(--bg); margin: 0; display: flex; height: 100vh; overflow: hidden; }
        
        /* 6. 顶部与侧边栏优化 */
        .sidebar { width: 260px; background: #2c3e50; color: white; display: flex; flex-direction: column; z-index: 100; }
        .sidebar-header { padding: 15px; background: #1a252f; text-align: center; height: 50px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; }
        .sidebar-header h3 { margin: 0; font-size: 16px; letter-spacing: 1px; }
        
        /* 6. 退出登录移至页面右上角 */
        .top-nav { position: absolute; top: 15px; right: 30px; z-index: 1000; }
        .logout-btn { color: #95a5a6; text-decoration: none; font-size: 14px; padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; transition: 0.3s; }
        .logout-btn:hover { color: var(--danger); border-color: var(--danger); background: #fff5f5; }

        .project-list { flex: 1; overflow-y: auto; padding: 10px; }
        .project-item { 
            padding: 10px 15px; margin-bottom: 5px; border-radius: 6px; 
            cursor: pointer; display: block; transition: 0.2s; text-decoration: none; color: #bdc3c7;
        }
        .project-item:hover { background: #3e4f5f; color: white; }
        .project-item.active { background: var(--primary); color: white; }

        .main-content { flex: 1; overflow-y: auto; padding: 30px; position: relative; padding-top: 60px; }
        .card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); margin-bottom: 25px; }
        
        /* 3. 修改项目名称内联样式 */
        .project-title-area { display: flex; align-items: center; gap: 15px; margin-bottom: 25px; }
        #renameForm { display: none; align-items: center; gap: 10px; width: 100%; }
        #renameForm input { width: 300px; font-size: 1.5em; font-weight: bold; margin: 0; }
        
        /* 5. 版本控件居左 */
        .version-bar { display: flex; align-items: center; justify-content: flex-start; gap: 15px; margin-bottom: 20px; background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #eee; }

        input[type="text"], textarea, select { width: 100%; padding: 10px; margin: 8px 0; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; }
        textarea { height: 120px; font-family: 'Consolas', 'Monaco', monospace; line-height: 1.5; }

        /* 4. 上传区域 */
        .upload-area { border: 3px dashed #ccc; background: #fafafa; padding: 40px 20px; text-align: center; border-radius: 15px; cursor: pointer; transition: 0.3s; margin: 20px 0; }
        .upload-area:hover, .upload-area.dragover { background: #f0f7ff; border-color: var(--primary); }
        .plus-icon { font-size: 50px; color: #ddd; margin-bottom: 10px; display: block; }

        /* 1. 图片原始尺寸查看模态框 */
        #imgModal { display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); justify-content: center; align-items: center; cursor: zoom-out; }
        #imgModal img { max-width: 90%; max-height: 90%; background: white; border-radius: 4px; box-shadow: 0 0 30px rgba(0,0,0,0.5); }

        .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; margin-top: 15px; }
        .preview-item { position: relative; cursor: zoom-in; transition: 0.3s; }
        .preview-item:hover { transform: translateY(-2px); }
        .preview-item img { width: 100%; height: 100px; object-fit: cover; border-radius: 8px; border: 1px solid #eee; }
        .delete-btn { position: absolute; top: -8px; right: -8px; background: var(--danger); color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 12px; }

        .btn { padding: 8px 16px; border-radius: 4px; border: none; cursor: pointer; font-weight: 600; display: inline-flex; align-items: center; gap: 5px; }
        .btn-primary { background: var(--primary); color: white; }
        .btn-danger { background: var(--danger); color: white; }
        .btn-save { background: #27ae60; color: white; width: 100%; padding: 12px; margin-top: 10px; font-size: 16px; }

        .exe-item { background: #f1f3f5; padding: 12px; border-radius: 8px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; border: 1px solid #e9ecef; }
        .exe-info { display: flex; align-items: center; gap: 10px; }
        .exe-size { font-size: 12px; color: #868e96; background: #e9ecef; padding: 2px 6px; border-radius: 4px; }
    </style>
</head>
<body>

    <div class="top-nav">
        <a href="?logout=1" class="logout-btn"><i class="fas fa-sign-out-alt"></i> 退出登录</a>
    </div>

    <div class="sidebar">
        <div class="sidebar-header"><h3>项目管理后台</h3></div>
        <div class="project-list">
            <?php foreach ($projectListWithEmoji as $p): ?>
                <a href="?project=<?= urlencode($p['name']) ?>" class="project-item <?= $currentProject === $p['name'] ? 'active' : '' ?>">
                    <?= $p['emoji'] ?> <?= $p['name'] ?>
                </a>
            <?php endforeach; ?>
            
            <form method="POST" style="margin-top: 20px; padding: 0 10px;">
                <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                <input type="text" name="project_name" placeholder="+ 新项目名称" style="background: #3e4f5f; border: none; color: white; font-size: 13px;">
                <button type="submit" name="new_project" class="btn btn-primary" style="width: 100%; margin-top: 5px; font-size: 13px;">创建</button>
            </form>
        </div>
    </div>

    <div class="main-content">
        <?php if ($currentProject): ?>
            <div class="project-title-area">
                <div id="titleDisplay">
                    <h2 style="display:inline; margin:0;"><?= $emojiText ?> <?= $currentProject ?></h2>
                    <button class="btn" onclick="toggleRename(true)" style="background:none; color:var(--primary); font-size: 1.2em;"><i class="fas fa-edit"></i></button>
                </div>
                <form id="renameForm" method="POST">
                    <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                    <input type="hidden" name="old_name" value="<?= $currentProject ?>">
                    <input type="text" name="new_name" value="<?= $currentProject ?>" required>
                    <button type="submit" name="rename_project" class="btn btn-primary">确定</button>
                    <button type="button" class="btn" onclick="toggleRename(false)">取消</button>
                </form>
            </div>

            <div class="card">
                <form method="POST">
                    <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                    <input type="hidden" name="proj" value="<?= $currentProject ?>">
                    <div style="display:grid; grid-template-columns: 100px 1fr; gap:15px; align-items:center;">
                        <label>项目图标:</label><input type="text" name="emoji" value="<?= $emojiText ?>" placeholder="Emoji">
                    </div>
                    <label>列表简介:</label><input type="text" name="summary" value="<?= htmlspecialchars($summaryText) ?>">
                    <label>详细介绍 (Markdown):</label><textarea name="info"><?= htmlspecialchars($generalInfoText) ?></textarea>
                    <label>运行环境依赖:</label><textarea name="requirements" style="height:60px;"><?= htmlspecialchars($requirementsText) ?></textarea>
                    <button type="submit" name="save_general_info" class="btn btn-save">保存所有基础信息</button>
                </form>
            </div>

            <div class="card">
                <div class="version-bar">
                    <span style="font-weight: bold; color: #666;">版本控制:</span>
                    <select style="width:140px; margin:0;" onchange="location.href='?project=<?=urlencode($currentProject)?>&version='+this.value">
                        <?php foreach ($projectVersions as $v): ?>
                            <option value="<?= $v ?>" <?= $currentVersion === $v ? 'selected' : '' ?>><?= $v ?></option>
                        <?php endforeach; ?>
                    </select>
                    <form method="POST" style="display:flex; gap:8px;">
                        <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                        <input type="hidden" name="proj" value="<?= $currentProject ?>">
                        <input type="text" name="version_name" placeholder="版本号" style="width:90px; margin:0;" required>
                        <button type="submit" name="new_version" class="btn btn-primary"><i class="fas fa-plus"></i></button>
                    </form>
                    <form method="POST" style="margin-left: auto;" onsubmit="return confirm('确定删除当前版本吗?')">
                        <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                        <input type="hidden" name="proj" value="<?= $currentProject ?>">
                        <input type="hidden" name="del_version" value="<?= $currentVersion ?>">
                        <button type="submit" name="delete_version" class="btn btn-danger" style="padding: 6px 10px; font-size: 12px;">删除此版本</button>
                    </form>
                </div>

                <?php if ($currentVersion): ?>
                    <h4>Python 源码</h4>
                    <form method="POST" id="codeForm">
                        <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                        <input type="hidden" name="proj" value="<?= $currentProject ?>">
                        <input type="hidden" name="version" value="<?= $currentVersion ?>">
                        <input type="hidden" name="is_pyw" id="isPywFlag" value="0">
                        <textarea id="codeEditor" name="code_content" style="height: 400px; background: #282c34; color: #abb2bf; font-size: 14px;"><?= htmlspecialchars($codeContent) ?></textarea>
                        <button type="submit" name="save_code" class="btn btn-save" style="background:#3498db">保存源码内容</button>
                    </form>

                    <div class="upload-area" id="dropZone" onclick="document.getElementById('fileInput').click()">
                        <i class="fas fa-cloud-upload-alt plus-icon"></i>
                        <p style="margin:0; font-weight:bold;">拖拽文件、粘贴截图、或点击上传</p>
                        <p style="margin:5px 0 0; font-size:12px; color:#999;">支持 .py / .pyw (自动填入) | .exe | 图片</p>
                        <input type="file" id="fileInput" multiple style="display:none" onchange="handleFiles(this.files)">
                        <div id="uploadStatus"></div>
                    </div>

                    <h4>截图展示</h4>
                    <div class="grid">
                        <?php foreach ($screenshots as $ss): ?>
                            <div class="preview-item" onclick="showOriginal('../projects/<?= $currentProject ?>/<?= $currentVersion ?>/screenshots/<?= $ss ?>')">
                                <img src="../projects/<?= $currentProject ?>/<?= $currentVersion ?>/screenshots/<?= $ss ?>">
                                <form method="POST" style="margin:0;">
                                    <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                                    <input type="hidden" name="proj" value="<?= $currentProject ?>">
                                    <input type="hidden" name="version" value="<?= $currentVersion ?>">
                                    <input type="hidden" name="type" value="screenshot">
                                    <input type="hidden" name="file" value="<?= $ss ?>">
                                    <button type="submit" name="delete_file" class="delete-btn" onclick="event.stopPropagation()"><i class="fas fa-times"></i></button>
                                </form>
                            </div>
                        <?php endforeach; ?>
                    </div>

                    <h4 style="margin-top:30px;">执行文件 (EXE)</h4>
                    <div id="exeList">
                        <?php foreach ($exeFiles as $exe): ?>
                            <div class="exe-item">
                                <div class="exe-info">
                                    <i class="fas fa-file-code" style="color:#74c0fc"></i>
                                    <span><?= $exe['name'] ?></span>
                                    <span class="exe-size"><?= $exe['size'] ?></span> </div>
                                <form method="POST" style="margin:0;">
                                    <input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
                                    <input type="hidden" name="proj" value="<?= $currentProject ?>">
                                    <input type="hidden" name="version" value="<?= $currentVersion ?>">
                                    <input type="hidden" name="type" value="exe">
                                    <input type="hidden" name="file" value="<?= $exe['name'] ?>">
                                    <button type="submit" name="delete_file" class="btn btn-danger" style="padding:4px 10px; font-size:12px;">删除</button>
                                </form>
                            </div>
                        <?php endforeach; ?>
                    </div>
                <?php endif; ?>
            </div>
        <?php else: ?>
            <div class="card" style="text-align: center; padding: 100px 0;">
                <i class="fas fa-folder-open" style="font-size: 60px; color: #eee; margin-bottom: 20px; display:block;"></i>
                <h3 style="color:#999;">请在侧边栏选择项目开始工作</h3>
            </div>
        <?php endif; ?>
    </div>

    <div id="imgModal" onclick="this.style.display='none'"><img id="modalImg" src=""></div>

    <script>
        // 1. 原始尺寸预览
        function showOriginal(src) {
            const modal = document.getElementById('imgModal');
            const img = document.getElementById('modalImg');
            img.src = src;
            modal.style.display = 'flex';
        }

        // 3. 重命名切换
        function toggleRename(show) {
            document.getElementById('titleDisplay').style.display = show ? 'none' : 'block';
            document.getElementById('renameForm').style.display = show ? 'flex' : 'none';
        }

        // 4. 增强上传逻辑
        window.addEventListener('paste', e => {
            const items = (e.clipboardData || e.originalEvent.clipboardData).items;
            for (let item of items) { if (item.kind === 'file') handleFiles([item.getAsFile()]); }
        });

        const dropZone = document.getElementById('dropZone');
        if (dropZone) {
            window.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('dragover'); });
            window.addEventListener('dragleave', e => { if (!e.relatedTarget) dropZone.classList.remove('dragover'); });
            dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('dragover'); handleFiles(e.dataTransfer.files); });
        }

        async function handleFiles(files) {
            const status = document.getElementById('uploadStatus');
            for (let file of files) {
                // 如果是 PY/PYW 文件,读取并自动填入源码区并提交
                const ext = file.name.split('.').pop().toLowerCase();
                if (ext === 'py' || ext === 'pyw') {
                    const reader = new FileReader();
                    reader.onload = function(e) {
                        document.getElementById('codeEditor').value = e.target.result;
                        document.getElementById('isPywFlag').value = (ext === 'pyw' ? "1" : "0");
                        status.innerHTML = `<span style="color:var(--primary)">检测到 ${file.name},正在同步源码...</span>`;
                        setTimeout(() => document.getElementById('codeForm').submit(), 500);
                    };
                    reader.readAsText(file);
                    continue;
                }

                // 常规上传 (图片/EXE)
                const formData = new FormData();
                formData.append('csrf_token', "<?= $csrfToken ?>");
                formData.append('proj', "<?= $currentProject ?>");
                formData.append('version', "<?= $currentVersion ?>");
                formData.append('action', 'upload_files');
                formData.append('file[]', file);
                
                status.innerHTML = `<span style="color:var(--primary)">正在上传 ${file.name}...</span>`;
                await fetch('index.php', { method: 'POST', body: formData });
                location.reload();
            }
        }
    </script>
</body>
</html>

admin/login.php:

查看代码
<?php
// admin/login.php
session_start();

if (isset($_POST['username']) && isset($_POST['password'])) {
    // 简单的硬编码用户名和密码 
    $username = '此处键入账号';
    $password = '此处键入密码';

    if ($_POST['username'] === $username && $_POST['password'] === $password) {
        $_SESSION['loggedin'] = true;
        $_SESSION['username'] = $username;
        header('Location: index.php');
        exit;
    } else {
        $error = '用户名或密码错误。';
    }
}

// 如果已经登录,直接跳转到管理页面
if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) {
    header('Location: index.php');
    exit;
}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>登录管理后台</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }
        .login-container { background-color: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; text-align: center; }
        .login-container h2 { margin-bottom: 20px; color: #333; }
        .login-container input[type="text"],
        .login-container input[type="password"] { width: calc(100% - 20px); padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }
        .login-container button { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1em; }
        .login-container button:hover { background-color: #0056b3; }
        .error-message { color: red; margin-bottom: 15px; }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>管理后台登录</h2>
        <?php if (isset($error)): ?>
            <p class="error-message"><?= htmlspecialchars($error) ?></p>
        <?php endif; ?>
        <form method="post">
            <input type="text" name="username" placeholder="用户名" required>
            <input type="password" name="password" placeholder="密码" required>
            <button type="submit">登录</button>
        </form>
    </div>
</body>
</html>

 

posted @ 2026-01-08 19:28  kkk-gitbook  阅读(0)  评论(0)    收藏  举报