This commit is contained in:
2026-06-24 17:45:24 +08:00
commit bc0223bf07
16 changed files with 1553 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
# 软件发布平台完善计划
## 项目目标
将现有的简单下载页面升级为功能完整的软件发布平台,包含后端管理、版本管理和配置系统。
## 技术方案
### 数据库选择
- 使用 **SQLite** 作为文件数据库(轻量、无需额外配置)
- 数据库文件路径:`data/database.db`
### 数据库设计
**apps 表** - 存储应用版本信息
```sql
CREATE TABLE apps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
original_name TEXT NOT NULL,
version TEXT,
description TEXT,
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP,
file_size INTEGER
);
```
**config 表** - 存储网站配置
```sql
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT
);
```
预设配置项:
- `site_title` - 网站标题(默认:"OPS 运营管理系统"
- `site_description` - 软件说明(默认:"点击下方按钮下载移动端 APP")
- `logo_path` - Logo 路径(默认:"logo.png"
### 目录结构
```
php_app_downloader/
├── index.php # 主页(显示所有版本)
├── download.php # 下载接口(支持按ID下载)
├── logo.png # 默认 Logo
├── admin/ # 管理后端目录
│ ├── index.php # 登录页面
│ ├── dashboard.php # 管理面板主页
│ ├── upload.php # 文件上传处理
│ ├── config.php # 配置管理页面
│ ├── delete.php # 删除应用接口
│ ├── logout.php # 登出
│ └── auth.php # 认证中间件
├── includes/ # 共享代码
│ ├── db.php # 数据库连接和初始化
│ └── functions.php # 通用函数
├── data/ # 数据存储目录
│ ├── apps/ # 上传的 APK 文件
│ ├── uploads/ # 上传的 Logo
│ └── database.db # SQLite 数据库
└── assets/ # 静态资源
└── admin.css # 管理后台样式
```
## 实现步骤
### 1. 创建基础设施(includes/
#### 1.1 数据库连接(includes/db.php
- 创建 SQLite 连接
- 自动初始化数据库结构(如果不存在)
- 插入默认配置数据
#### 1.2 通用函数(includes/functions.php
- `getConfig($key)` - 获取配置
- `updateConfig($key, $value)` - 更新配置
- `formatFileSize($bytes)` - 格式化文件大小
- `isLoggedIn()` - 检查登录状态
### 2. 创建目录结构
- 创建 `data/`, `data/apps/`, `data/uploads/`, `admin/`, `includes/`, `assets/`
- 设置适当的权限(777 for data/ 及子目录)
### 3. 实现管理后端(admin/
#### 3.1 登录页面(admin/index.php
- 简洁的登录表单
- 默认用户名/密码:admin/admin
- 使用 Session 管理登录状态
- 成功后跳转到 dashboard.php
#### 3.2 认证中间件(admin/auth.php
- 检查 Session 中的登录状态
- 未登录则重定向到登录页面
- 被其他后台页面 include
#### 3.3 管理面板(admin/dashboard.php
- 显示所有已上传的应用版本(表格形式)
- 上传新应用的表单(文件 + 版本号 + 描述)
- 删除应用的功能
- 链接到配置页面
#### 3.4 文件上传处理(admin/upload.php
- 接收 POST 请求
- 验证文件类型(仅允许 .apk
- 生成唯一文件名(时间戳_原文件名)
- 移动文件到 data/apps/
- 插入数据库记录
- 返回 JSON 响应或重定向
#### 3.5 配置管理(admin/config.php
- 表单显示当前配置
- 网站标题输入框
- 软件说明文本框
- Logo 上传(支持 jpg/png/gif
- 保存到数据库
- Logo 上传到 data/uploads/ 并更新配置
#### 3.6 删除接口(admin/delete.php
- 接收 app ID
- 从数据库删除记录
- 删除物理文件
- 重定向回 dashboard
#### 3.7 登出(admin/logout.php
- 销毁 Session
- 重定向到登录页面
### 4. 更新前端
#### 4.1 主页(index.php
**功能改进:**
- 从数据库读取配置(标题、说明、Logo)
- 显示所有已发布的应用版本列表(不只是最新的一个)
- 每个版本显示:版本号、描述、上传时间、文件大小
- 最新版本在最前面(ORDER BY upload_time DESC
**样式重写:**
- 现代化的卡片布局
- 响应式设计(移动端友好)
- 更好的视觉层次
- 使用渐变背景和阴影效果
- 每个版本一个下载按钮
#### 4.2 下载接口(download.php
**功能改进:**
- 接收 `id` 参数(GET 请求)
- 从数据库查询应用信息
- 返回文件路径用于下载
- 保留向后兼容(如果没有 id,返回最新版本)
### 5. 管理后台样式(assets/admin.css
- 简洁的管理界面样式
- 表格样式
- 表单样式
- 按钮和交互元素
- 侧边栏导航(可选)
## 安全考虑
1. **文件上传安全:**
- 验证文件扩展名
- 限制文件大小
- 使用随机文件名防止覆盖
2. **认证安全:**
- 使用 Session 管理
- 密码硬编码(后续可改为数据库存储的加密密码)
3. **SQL 注入防护:**
- 使用 PDO 预处理语句
4. **XSS 防护:**
- 输出时使用 htmlspecialchars()
## 实现顺序
1. ✅ 创建目录结构
2. ✅ 实现 includes/db.php(数据库初始化)
3. ✅ 实现 includes/functions.php
4. ✅ 实现 admin/index.php(登录页面)
5. ✅ 实现 admin/auth.php
6. ✅ 实现 admin/dashboard.php
7. ✅ 实现 admin/upload.php
8. ✅ 实现 admin/config.php
9. ✅ 实现 admin/delete.php
10. ✅ 实现 admin/logout.php
11. ✅ 更新 index.php(前端)
12. ✅ 更新 download.php
13. ✅ 创建 assets/admin.css
14. ✅ 测试所有功能
## 验证清单
- [ ] 可以登录管理后台(admin/admin
- [ ] 可以上传 APK 文件
- [ ] 上传的文件保存在 data/apps/
- [ ] 主页显示所有版本,最新的在前
- [ ] 可以下载各个版本
- [ ] 可以在后台删除版本
- [ ] 可以配置网站标题
- [ ] 可以配置软件说明
- [ ] 可以上传和更换 Logo
- [ ] 前端样式美观且响应式
## 预计文件清单
**新建文件:**
- admin/index.php
- admin/dashboard.php
- admin/upload.php
- admin/config.php
- admin/delete.php
- admin/logout.php
- admin/auth.php
- includes/db.php
- includes/functions.php
- assets/admin.css
**修改文件:**
- index.php
- download.php
**创建目录:**
- data/
- data/apps/
- data/uploads/
- admin/
- includes/
- assets/
+51
View File
@@ -0,0 +1,51 @@
# 数据目录 - 包含上传的文件和数据库
data/
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
# Temporary files
*.tmp
*.bak
*.backup
# Environment files
.env
.env.local
+190
View File
@@ -0,0 +1,190 @@
# 软件发布平台
一个简单易用的软件发布管理系统,支持多版本管理和在线下载。
## 功能特性
**后台管理系统**
- 用户登录验证(默认用户名/密码:admin/admin
- 上传 APK 文件到服务器
- 版本管理(查看、删除)
- 网站配置(标题、说明、Logo
**前端展示**
- 显示所有已发布的应用版本
- 最新版本优先显示
- 版本详情(版本号、描述、上传时间、文件大小)
- 一键下载功能
**数据存储**
- 使用 SQLite 文件数据库
- APK 文件存储在 `data/apps/` 目录
- Logo 文件存储在 `data/uploads/` 目录
## 目录结构
```
php_app_downloader/
├── index.php # 主页(显示所有版本)
├── download.php # 下载接口
├── logo.png # 默认 Logo
├── admin/ # 管理后台
│ ├── index.php # 登录页面
│ ├── dashboard.php # 管理面板
│ ├── upload.php # 文件上传处理
│ ├── config.php # 配置管理
│ ├── delete.php # 删除应用
│ ├── logout.php # 退出登录
│ └── auth.php # 认证中间件
├── includes/ # 共享代码
│ ├── db.php # 数据库连接
│ └── functions.php # 通用函数
├── data/ # 数据存储(需要写权限)
│ ├── apps/ # APK 文件
│ ├── uploads/ # 上传的 Logo
│ └── database.db # SQLite 数据库
└── assets/
└── admin.css # 管理后台样式
```
## 使用说明
### 1. 环境要求
- PHP 7.0+
- SQLite 支持(PHP 默认包含)
- Web 服务器(Apache/Nginx
### 2. 安装步骤
1. 将项目文件上传到 Web 服务器
2. 确保 `data/` 目录有写权限(已设置为 777
3. 访问网站首页,数据库会自动初始化
### 3. 管理后台
访问 `/admin/` 进入管理后台
**默认登录信息:**
- 用户名:`admin`
- 密码:`admin`
**功能说明:**
#### 上传应用
1. 在管理面板点击"上传新应用"
2. 选择 APK 文件
3. 填写版本号(可选)
4. 填写版本描述(可选)
5. 点击"上传应用"
#### 管理版本
- 查看所有已发布的版本
- 下载任意版本
- 删除不需要的版本
#### 网站配置
1. 点击"网站配置"
2. 修改网站标题
3. 修改软件说明
4. 上传新的 Logo(支持 JPG/PNG/GIF
5. 点击"保存配置"
### 4. 前端使用
- 访问网站首页查看所有版本
- 点击"立即下载"按钮下载对应版本
- 右下角有"管理后台"快捷入口
## 数据库结构
### apps 表
存储应用版本信息
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER | 主键,自增 |
| filename | TEXT | 存储的文件名 |
| original_name | TEXT | 原始文件名 |
| version | TEXT | 版本号 |
| description | TEXT | 版本描述 |
| upload_time | DATETIME | 上传时间 |
| file_size | INTEGER | 文件大小(字节)|
### config 表
存储网站配置
| 字段 | 类型 | 说明 |
|------|------|------|
| key | TEXT | 配置键(主键)|
| value | TEXT | 配置值 |
**预设配置项:**
- `site_title` - 网站标题
- `site_description` - 软件说明
- `logo_path` - Logo 路径
## API 接口
### 下载接口
`GET download.php?id={app_id}`
**参数:**
- `id` (可选) - 应用 ID,不传则返回最新版本
**响应示例:**
```json
{
"success": true,
"file": "data/apps/1719223456_app.apk",
"filename": "app.apk",
"version": "1.0.0",
"date": "2024-06-24 17:30:56"
}
```
## 安全建议
1. **修改默认密码**
- 当前密码硬编码在 `admin/index.php`
- 建议修改为复杂密码
2. **限制文件上传**
- 当前仅允许 `.apk` 文件
- 可根据需要调整文件类型限制
3. **数据备份**
- 定期备份 `data/` 目录
- 包含数据库和所有上传的文件
4. **访问控制**
- 建议通过 Web 服务器配置限制 `/admin/` 访问
- 可添加 IP 白名单
## 技术栈
- **后端:** PHP + SQLite
- **前端:** 原生 HTML/CSS/JavaScript
- **样式:** 现代化渐变设计,响应式布局
## 更新日志
### v2.0 (2024-06-24)
- ✅ 新增管理后台系统
- ✅ 新增用户登录功能
- ✅ 支持多版本管理
- ✅ 支持在线上传 APK
- ✅ 支持网站配置管理
- ✅ 重写前端样式
- ✅ 使用 SQLite 数据库
### v1.0
- 基础下载功能
- 显示最新版本
## 许可证
MIT License
---
如有问题或建议,欢迎反馈!
+9
View File
@@ -0,0 +1,9 @@
<?php
// 认证中间件 - 检查登录状态
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: index.php');
exit;
}
+111
View File
@@ -0,0 +1,111 @@
<?php
require_once 'auth.php';
require_once '../includes/db.php';
require_once '../includes/functions.php';
$success = '';
$error = '';
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$site_title = $_POST['site_title'] ?? '';
$site_description = $_POST['site_description'] ?? '';
// 更新基本配置
updateConfig('site_title', $site_title);
updateConfig('site_description', $site_description);
// 处理 Logo 上传
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
$logo = $_FILES['logo'];
$ext = strtolower(pathinfo($logo['name'], PATHINFO_EXTENSION));
// 验证文件类型
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
$logo_filename = 'logo_' . time() . '.' . $ext;
$logo_path = '../data/uploads/' . $logo_filename;
if (move_uploaded_file($logo['tmp_name'], $logo_path)) {
// 删除旧 logo(如果在 data/uploads/ 目录)
$old_logo = getConfig('logo_path');
if (strpos($old_logo, 'data/uploads/') === 0) {
$old_logo_path = '../' . $old_logo;
if (file_exists($old_logo_path)) {
unlink($old_logo_path);
}
}
updateConfig('logo_path', 'data/uploads/' . $logo_filename);
$success = '配置保存成功!';
} else {
$error = 'Logo 上传失败';
}
} else {
$error = 'Logo 只支持 JPG、PNG、GIF 格式';
}
} else {
$success = '配置保存成功!';
}
}
// 获取当前配置
$site_title = getConfig('site_title', 'OPS 运营管理系统');
$site_description = getConfig('site_description', '点击下方按钮下载移动端 APP');
$logo_path = getConfig('logo_path', 'logo.png');
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网站配置</title>
<link rel="stylesheet" href="../assets/admin.css">
</head>
<body>
<div class="admin-container">
<header class="admin-header">
<h1>网站配置</h1>
<div class="header-actions">
<a href="dashboard.php" class="btn btn-secondary">返回管理面板</a>
<a href="logout.php" class="btn btn-secondary">退出</a>
</div>
</header>
<main class="admin-main">
<?php if ($success): ?>
<div class="alert alert-success"><?php echo e($success); ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-error"><?php echo e($error); ?></div>
<?php endif; ?>
<section class="config-section">
<h2>基本设置</h2>
<form method="POST" enctype="multipart/form-data" class="config-form">
<div class="form-group">
<label>网站标题 *</label>
<input type="text" name="site_title" value="<?php echo e($site_title); ?>" required>
</div>
<div class="form-group">
<label>软件说明</label>
<textarea name="site_description" rows="4"><?php echo e($site_description); ?></textarea>
</div>
<div class="form-group">
<label>网站 Logo</label>
<div class="logo-preview">
<img src="../<?php echo e($logo_path); ?>" alt="Logo" style="max-width: 200px; max-height: 200px;">
</div>
<input type="file" name="logo" accept="image/jpeg,image/png,image/gif">
<small class="form-hint">支持 JPG、PNG、GIF 格式</small>
</div>
<button type="submit" class="btn btn-primary">保存配置</button>
</form>
</section>
</main>
</div>
</body>
</html>
+91
View File
@@ -0,0 +1,91 @@
<?php
require_once 'auth.php';
require_once '../includes/db.php';
require_once '../includes/functions.php';
// 获取所有应用列表
$stmt = $db->query("SELECT * FROM apps ORDER BY upload_time DESC");
$apps = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理面板</title>
<link rel="stylesheet" href="../assets/admin.css">
</head>
<body>
<div class="admin-container">
<header class="admin-header">
<h1>管理面板</h1>
<div class="header-actions">
<span>欢迎,<?php echo e($_SESSION['username']); ?></span>
<a href="config.php" class="btn btn-secondary">网站配置</a>
<a href="logout.php" class="btn btn-secondary">退出</a>
</div>
</header>
<main class="admin-main">
<section class="upload-section">
<h2>上传新应用</h2>
<form action="upload.php" method="POST" enctype="multipart/form-data" class="upload-form">
<div class="form-row">
<div class="form-group">
<label>APK 文件 *</label>
<input type="file" name="apk_file" accept=".apk" required>
</div>
<div class="form-group">
<label>版本号</label>
<input type="text" name="version" placeholder="例如:1.0.0">
</div>
</div>
<div class="form-group">
<label>版本描述</label>
<textarea name="description" rows="3" placeholder="描述此版本的更新内容..."></textarea>
</div>
<button type="submit" class="btn btn-primary">上传应用</button>
</form>
</section>
<section class="apps-section">
<h2>已发布应用 (<?php echo count($apps); ?>)</h2>
<?php if (empty($apps)): ?>
<p class="empty-state">暂无已发布的应用</p>
<?php else: ?>
<table class="apps-table">
<thead>
<tr>
<th>文件名</th>
<th>版本</th>
<th>描述</th>
<th>大小</th>
<th>上传时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($apps as $app): ?>
<tr>
<td><?php echo e($app['original_name']); ?></td>
<td><?php echo e($app['version'] ?: '-'); ?></td>
<td class="description"><?php echo e($app['description'] ?: '-'); ?></td>
<td><?php echo formatFileSize($app['file_size']); ?></td>
<td><?php echo date('Y-m-d H:i', strtotime($app['upload_time'])); ?></td>
<td>
<a href="../data/apps/<?php echo e($app['filename']); ?>" class="btn-small btn-info" download>下载</a>
<a href="delete.php?id=<?php echo $app['id']; ?>"
class="btn-small btn-danger"
onclick="return confirm('确定要删除此应用吗?')">删除</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</section>
</main>
</div>
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
<?php
require_once 'auth.php';
require_once '../includes/db.php';
$id = $_GET['id'] ?? 0;
if ($id > 0) {
// 获取应用信息
$stmt = $db->prepare("SELECT * FROM apps WHERE id = ?");
$stmt->execute([$id]);
$app = $stmt->fetch(PDO::FETCH_ASSOC);
if ($app) {
// 删除物理文件
$file_path = '../data/apps/' . $app['filename'];
if (file_exists($file_path)) {
unlink($file_path);
}
// 从数据库删除
$stmt = $db->prepare("DELETE FROM apps WHERE id = ?");
$stmt->execute([$id]);
}
}
header('Location: dashboard.php');
exit;
+137
View File
@@ -0,0 +1,137 @@
<?php
session_start();
// 如果已登录,跳转到管理面板
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
header('Location: dashboard.php');
exit;
}
$error = '';
// 处理登录请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// 简单的硬编码验证(默认 admin/admin
if ($username === 'admin' && $password === 'admin') {
$_SESSION['admin_logged_in'] = true;
$_SESSION['username'] = $username;
header('Location: dashboard.php');
exit;
} else {
$error = '用户名或密码错误';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理后台登录</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.login-container {
background: #fff;
border-radius: 16px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 400px;
}
h1 {
font-size: 24px;
color: #333;
margin-bottom: 30px;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="password"]:focus {
outline: none;
border-color: #667eea;
}
button {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s;
}
button:hover {
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
.error {
background: #fee;
color: #c33;
padding: 12px;
border-radius: 8px;
font-size: 14px;
margin-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="login-container">
<h1>管理后台登录</h1>
<?php if ($error): ?>
<div class="error"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" required autofocus>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required>
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
<?php
// 登出
session_start();
session_destroy();
header('Location: index.php');
exit;
+50
View File
@@ -0,0 +1,50 @@
<?php
require_once 'auth.php';
require_once '../includes/db.php';
// 处理文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_FILES['apk_file']) || $_FILES['apk_file']['error'] !== UPLOAD_ERR_OK) {
die('文件上传失败');
}
$file = $_FILES['apk_file'];
$version = $_POST['version'] ?? '';
$description = $_POST['description'] ?? '';
// 验证文件类型
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($ext !== 'apk') {
die('只允许上传 APK 文件');
}
// 生成唯一文件名
$filename = time() . '_' . $file['name'];
$target_path = '../data/apps/' . $filename;
// 移动文件
if (!move_uploaded_file($file['tmp_name'], $target_path)) {
die('文件保存失败');
}
// 插入数据库
$stmt = $db->prepare("
INSERT INTO apps (filename, original_name, version, description, file_size)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([
$filename,
$file['name'],
$version,
$description,
$file['size']
]);
// 重定向回管理面板
header('Location: dashboard.php');
exit;
}
// 如果不是 POST 请求,重定向回管理面板
header('Location: dashboard.php');
exit;
+280
View File
@@ -0,0 +1,280 @@
/* 管理后台样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f7fa;
color: #333;
}
.admin-container {
min-height: 100vh;
}
/* 头部 */
.admin-header {
background: #fff;
padding: 20px 40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
}
.admin-header h1 {
font-size: 24px;
color: #333;
}
.header-actions {
display: flex;
gap: 16px;
align-items: center;
}
.header-actions span {
color: #666;
font-size: 14px;
}
/* 主内容区 */
.admin-main {
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
}
/* 区块 */
section {
background: #fff;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
section h2 {
font-size: 20px;
margin-bottom: 20px;
color: #333;
}
/* 表单 */
.form-group {
margin-bottom: 20px;
}
.form-row {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
}
label {
display: block;
color: #555;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
input[type="text"],
input[type="file"],
textarea {
width: 100%;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
textarea:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
font-family: inherit;
}
.form-hint {
display: block;
margin-top: 6px;
color: #999;
font-size: 12px;
}
/* 按钮 */
.btn {
display: inline-block;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
text-decoration: none;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #f0f0f0;
color: #666;
}
.btn-secondary:hover {
background: #e5e5e5;
}
.btn-small {
padding: 6px 12px;
font-size: 12px;
border-radius: 4px;
}
.btn-info {
background: #3b82f6;
color: #fff;
}
.btn-info:hover {
background: #2563eb;
}
.btn-danger {
background: #ef4444;
color: #fff;
}
.btn-danger:hover {
background: #dc2626;
}
/* 表格 */
.apps-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.apps-table thead {
background: #f9fafb;
}
.apps-table th,
.apps-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.apps-table th {
font-weight: 600;
color: #555;
font-size: 13px;
}
.apps-table td {
font-size: 14px;
}
.apps-table td.description {
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.apps-table tbody tr:hover {
background: #f9fafb;
}
.apps-table td:last-child {
white-space: nowrap;
}
.apps-table .btn-small {
margin-right: 6px;
}
/* 提示信息 */
.alert {
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 20px;
font-size: 14px;
}
.alert-success {
background: #d1fae5;
color: #065f46;
border: 1px solid #6ee7b7;
}
.alert-error {
background: #fee;
color: #c33;
border: 1px solid #fca5a5;
}
/* 空状态 */
.empty-state {
text-align: center;
color: #999;
padding: 40px;
font-size: 14px;
}
/* Logo 预览 */
.logo-preview {
margin: 10px 0;
padding: 20px;
background: #f9fafb;
border-radius: 8px;
text-align: center;
}
.logo-preview img {
display: block;
margin: 0 auto;
}
/* 响应式 */
@media (max-width: 768px) {
.admin-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.form-row {
grid-template-columns: 1fr;
}
.apps-table {
font-size: 12px;
}
.apps-table th,
.apps-table td {
padding: 8px;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
require_once 'includes/db.php';
header('Content-Type: application/json; charset=utf-8');
$id = $_GET['id'] ?? null;
try {
if ($id) {
// 按 ID 下载指定版本
$stmt = $db->prepare("SELECT * FROM apps WHERE id = ?");
$stmt->execute([$id]);
$app = $stmt->fetch(PDO::FETCH_ASSOC);
if ($app && file_exists('data/apps/' . $app['filename'])) {
echo json_encode([
'success' => true,
'file' => 'data/apps/' . rawurlencode($app['filename']),
'filename' => $app['original_name'],
'version' => $app['version'],
'date' => date('Y-m-d H:i:s', strtotime($app['upload_time']))
]);
} else {
echo json_encode([
'success' => false,
'message' => 'APK 文件不存在'
]);
}
} else {
// 兼容旧版:返回最新版本
$stmt = $db->query("SELECT * FROM apps ORDER BY upload_time DESC LIMIT 1");
$app = $stmt->fetch(PDO::FETCH_ASSOC);
if ($app && file_exists('data/apps/' . $app['filename'])) {
echo json_encode([
'success' => true,
'file' => 'data/apps/' . rawurlencode($app['filename']),
'filename' => $app['original_name'],
'version' => $app['version'],
'date' => date('Y-m-d H:i:s', strtotime($app['upload_time']))
]);
} else {
echo json_encode([
'success' => false,
'message' => '未找到 APK 文件'
]);
}
}
} catch (Exception $e) {
echo json_encode([
'success' => false,
'message' => '查询失败: ' . $e->getMessage()
]);
}
+49
View File
@@ -0,0 +1,49 @@
<?php
// 数据库连接和初始化
$db_path = __DIR__ . '/../data/database.db';
$is_new_db = !file_exists($db_path);
try {
$db = new PDO('sqlite:' . $db_path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 如果是新数据库,初始化表结构
if ($is_new_db) {
// 创建 apps 表
$db->exec("
CREATE TABLE IF NOT EXISTS apps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
original_name TEXT NOT NULL,
version TEXT,
description TEXT,
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP,
file_size INTEGER
)
");
// 创建 config 表
$db->exec("
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT
)
");
// 插入默认配置
$defaults = [
['site_title', 'OPS 运营管理系统'],
['site_description', '点击下方按钮下载移动端 APP'],
['logo_path', 'logo.png']
];
$stmt = $db->prepare("INSERT INTO config (key, value) VALUES (?, ?)");
foreach ($defaults as $config) {
$stmt->execute($config);
}
}
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}
+51
View File
@@ -0,0 +1,51 @@
<?php
// 通用函数
/**
* 获取配置值
*/
function getConfig($key, $default = '') {
global $db;
$stmt = $db->prepare("SELECT value FROM config WHERE key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['value'] : $default;
}
/**
* 更新配置值
*/
function updateConfig($key, $value) {
global $db;
$stmt = $db->prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)");
return $stmt->execute([$key, $value]);
}
/**
* 格式化文件大小
*/
function formatFileSize($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
return number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
return number_format($bytes / 1024, 2) . ' KB';
} else {
return $bytes . ' B';
}
}
/**
* 检查是否已登录
*/
function isLoggedIn() {
return isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
}
/**
* 安全输出 HTML
*/
function e($string) {
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
+215
View File
@@ -0,0 +1,215 @@
<?php
require_once 'includes/db.php';
require_once 'includes/functions.php';
// 获取网站配置
$site_title = getConfig('site_title', 'OPS 运营管理系统');
$site_description = getConfig('site_description', '点击下方按钮下载移动端 APP');
$logo_path = getConfig('logo_path', 'logo.png');
// 获取所有应用版本
$stmt = $db->query("SELECT * FROM apps ORDER BY upload_time DESC");
$apps = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo e($site_title); ?></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 40px 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.header {
background: #fff;
border-radius: 20px;
padding: 40px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
margin-bottom: 30px;
}
.logo {
width: 120px;
height: 120px;
margin-bottom: 20px;
object-fit: contain;
}
.title {
font-size: 28px;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.description {
font-size: 14px;
color: #666;
line-height: 1.6;
}
.versions {
display: grid;
gap: 20px;
}
.version-card {
background: #fff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
transition: transform 0.2s, box-shadow 0.2s;
}
.version-card:hover {
transform: translateY(-4px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
}
.version-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.version-info h3 {
font-size: 18px;
color: #333;
margin-bottom: 4px;
}
.version-number {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.version-meta {
display: flex;
gap: 16px;
font-size: 12px;
color: #999;
margin-bottom: 12px;
}
.version-description {
color: #666;
font-size: 14px;
line-height: 1.6;
margin-bottom: 16px;
}
.download-btn {
display: inline-block;
padding: 12px 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
font-size: 15px;
font-weight: 500;
border-radius: 50px;
text-decoration: none;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.download-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.download-btn:active {
transform: translateY(0);
}
.empty-state {
background: #fff;
border-radius: 16px;
padding: 60px 40px;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
color: #999;
}
.admin-link {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.9);
color: #667eea;
padding: 10px 20px;
border-radius: 20px;
text-decoration: none;
font-size: 14px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transition: transform 0.2s;
}
.admin-link:hover {
transform: translateY(-2px);
background: #fff;
}
@media (max-width: 600px) {
body {
padding: 20px 10px;
}
.header {
padding: 30px 20px;
}
.title {
font-size: 22px;
}
.version-card {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="<?php echo e($logo_path); ?>" alt="Logo" class="logo">
<h1 class="title"><?php echo e($site_title); ?></h1>
<p class="description"><?php echo nl2br(e($site_description)); ?></p>
</div>
<div class="versions">
<?php if (empty($apps)): ?>
<div class="empty-state">
<p>暂无可用版本</p>
</div>
<?php else: ?>
<?php foreach ($apps as $app): ?>
<div class="version-card">
<div class="version-header">
<div class="version-info">
<h3><?php echo e($app['original_name']); ?></h3>
<?php if ($app['version']): ?>
<span class="version-number">v<?php echo e($app['version']); ?></span>
<?php endif; ?>
</div>
</div>
<div class="version-meta">
<span>📅 <?php echo date('Y-m-d H:i', strtotime($app['upload_time'])); ?></span>
<span>📦 <?php echo formatFileSize($app['file_size']); ?></span>
</div>
<?php if ($app['description']): ?>
<div class="version-description">
<?php echo nl2br(e($app['description'])); ?>
</div>
<?php endif; ?>
<a href="download.php?id=<?php echo $app['id']; ?>" class="download-btn">立即下载</a>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<a href="admin/" class="admin-link">管理后台</a>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB