一个基于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 上传,并利用FileReaderAPI 实现.py文件的即时读取与代码区自动填充。
2. projects/ 目录 (数据层)
这是系统的“数据库”。
-
分层设计: 采用
项目 -> 版本 -> 附件的三级结构。 -
自动分拣: 后端逻辑会根据文件后缀,自动将上传的文件放入对应的子目录:
-
图片 $\rightarrow$
/screenshots/ -
可执行程序 $\rightarrow$
/exe/ -
源码 $\rightarrow$ 根目录
-
3. 命名约定
-
源码文件: 采用
项目名_版本号.py的格式自动命名。 -
重命名逻辑: 当你在后台修改项目名称时,
admin/index.php会调用rename()函数更改对应的文件夹名称,由于所有路径都是动态生成的,修改后前台页面会自动同步。
部署建议
-
权限设置: 确保
projects/文件夹对 Web 服务器(如www-data或apache用户)具有写权限。 -
安全防护:
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()">×</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>
浙公网安备 33010602011771号