Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b2b158e73 | |||
| 136728798a | |||
| b733f81ed8 | |||
| 48caa4168c | |||
| 04c0c3cae5 |
51
README.md
51
README.md
@@ -5,7 +5,8 @@
|
|||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- ✅ 管理员登录(密码:admin123)
|
- ✅ 管理员登录(密码:admin123)
|
||||||
- ✅ App上传(支持.apk、.ipa、.zip文件,最大100MB)
|
- ✅ App上传(支持.apk、.ipa、.zip文件,最大1GB)
|
||||||
|
- ✅ App删除功能
|
||||||
- ✅ 无需登录即可下载
|
- ✅ 无需登录即可下载
|
||||||
- ✅ 响应式设计
|
- ✅ 响应式设计
|
||||||
- ✅ 纯前端实现,无框架依赖
|
- ✅ 纯前端实现,无框架依赖
|
||||||
@@ -13,7 +14,16 @@
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 1. 启动前端服务
|
### 1. 启动后端服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd background
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
后端服务将在 http://localhost:6903 上运行
|
||||||
|
|
||||||
|
### 2. 启动前端服务
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 使用Python启动简单HTTP服务器
|
# 使用Python启动简单HTTP服务器
|
||||||
@@ -27,7 +37,7 @@ npx http-server -p 8000
|
|||||||
|
|
||||||
#### 后端要求
|
#### 后端要求
|
||||||
|
|
||||||
- 端口:3000
|
- 端口:6903 (修改自默认的3000端口)
|
||||||
- 无需鉴权
|
- 无需鉴权
|
||||||
- 支持CORS跨域请求
|
- 支持CORS跨域请求
|
||||||
|
|
||||||
@@ -35,19 +45,28 @@ npx http-server -p 8000
|
|||||||
|
|
||||||
| 方法 | 路径 | 描述 | 请求体 | 响应 |
|
| 方法 | 路径 | 描述 | 请求体 | 响应 |
|
||||||
|------|------|------|--------|------|
|
|------|------|------|--------|------|
|
||||||
| GET | /api/apps | 获取App列表 | - | `[{id: string, name: string, fileName: string, date: string}]` |
|
| GET | /api/apps | 获取App列表 | - | `[{id: string, name: string, fileName: string, date: string, filePath: string}]` |
|
||||||
| POST | /api/apps | 上传App | multipart/form-data<br>`name`: App名称<br>`file`: 文件 | `{success: boolean, message?: string}` |
|
| POST | /api/apps | 上传App | multipart/form-data<br>`name`: App名称<br>`file`: 文件 | `{success: boolean, message?: string}` |
|
||||||
|
| DELETE | /api/apps/:id | 删除指定App | - | `{success: boolean, message?: string}` |
|
||||||
| GET | /api/apps/:id | 下载指定App | - | 文件流 |
|
| GET | /api/apps/:id | 下载指定App | - | 文件流 |
|
||||||
|
| GET | /api/docs | 获取文档内容 | - | `[{docBody: string}]` |
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- HTML5
|
- HTML5
|
||||||
- CSS3
|
- CSS3
|
||||||
- JavaScript (ES6+)
|
- JavaScript (ES6+)
|
||||||
|
- Go (后端)
|
||||||
|
- Gin (Web框架)
|
||||||
|
|
||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
|
├── background/ # 后端目录
|
||||||
|
│ ├── main.go # 后端主程序
|
||||||
|
│ ├── apps.json # 应用数据持久化文件
|
||||||
|
│ ├── files/ # 存储上传的文件
|
||||||
|
│ └── docs/ # 存储文档文件
|
||||||
├── index.html # 主页面
|
├── index.html # 主页面
|
||||||
├── style.css # 样式文件
|
├── style.css # 样式文件
|
||||||
├── script.js # 核心逻辑
|
├── script.js # 核心逻辑
|
||||||
@@ -74,6 +93,12 @@ npx http-server -p 8000
|
|||||||
1. 在下载区域找到要下载的App
|
1. 在下载区域找到要下载的App
|
||||||
2. 点击"下载"按钮即可开始下载
|
2. 点击"下载"按钮即可开始下载
|
||||||
|
|
||||||
|
### 删除App
|
||||||
|
|
||||||
|
1. 点击App列表中的"删除"按钮
|
||||||
|
2. 在确认对话框中点击"确定"
|
||||||
|
3. App将从列表和服务器中被删除
|
||||||
|
|
||||||
## 自定义配置
|
## 自定义配置
|
||||||
|
|
||||||
### 修改管理员密码
|
### 修改管理员密码
|
||||||
@@ -92,10 +117,24 @@ const ADMIN_PASSWORD = 'your_new_password';
|
|||||||
const API_BASE_URL = 'http://your-backend-url:port/api';
|
const API_BASE_URL = 'http://your-backend-url:port/api';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 修改后端端口
|
||||||
|
|
||||||
|
在 `background/main.go` 中修改:
|
||||||
|
|
||||||
|
```go
|
||||||
|
port = ":6903" // 修改为你想要的端口
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据持久化
|
||||||
|
|
||||||
|
- 应用列表数据保存在 `background/apps.json` 文件中
|
||||||
|
- 上传的文件保存在 `background/files/` 目录中
|
||||||
|
- 文档内容保存在 `background/docs/` 目录中
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. 默认使用localStorage存储,数据只保存在浏览器中
|
1. 默认使用localStorage存储登录状态,数据只保存在浏览器中
|
||||||
2. 建议部署后端API以实现持久化存储
|
2. 应用数据持久化在后端的apps.json文件中
|
||||||
3. 生产环境中请修改默认密码
|
3. 生产环境中请修改默认密码
|
||||||
4. 建议添加HTTPS支持,尤其是在公网环境中
|
4. 建议添加HTTPS支持,尤其是在公网环境中
|
||||||
|
|
||||||
|
|||||||
@@ -70,11 +70,8 @@ func loadApps() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存apps数据
|
// 保存apps数据(调用者需要持有锁)
|
||||||
func saveApps() error {
|
func saveApps() error {
|
||||||
appsMutex.Lock()
|
|
||||||
defer appsMutex.Unlock()
|
|
||||||
|
|
||||||
file, err := os.Create(jsonFile)
|
file, err := os.Create(jsonFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create apps.json: %w", err)
|
return fmt.Errorf("failed to create apps.json: %w", err)
|
||||||
@@ -223,13 +220,13 @@ func uploadApp(c *gin.Context) {
|
|||||||
|
|
||||||
appsMutex.Lock()
|
appsMutex.Lock()
|
||||||
apps = append(apps, newApp)
|
apps = append(apps, newApp)
|
||||||
appsMutex.Unlock()
|
|
||||||
|
|
||||||
// 保存到文件
|
// 保存到文件
|
||||||
if err := saveApps(); err != nil {
|
if err := saveApps(); err != nil {
|
||||||
|
appsMutex.Unlock()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
appsMutex.Unlock()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
}
|
}
|
||||||
@@ -308,13 +305,13 @@ func deleteApp(c *gin.Context) {
|
|||||||
// 从apps切片中删除该应用
|
// 从apps切片中删除该应用
|
||||||
appsMutex.Lock()
|
appsMutex.Lock()
|
||||||
apps = append(apps[:targetIndex], apps[targetIndex+1:]...)
|
apps = append(apps[:targetIndex], apps[targetIndex+1:]...)
|
||||||
appsMutex.Unlock()
|
|
||||||
|
|
||||||
// 保存到文件
|
// 保存到文件
|
||||||
if err := saveApps(); err != nil {
|
if err := saveApps(); err != nil {
|
||||||
|
appsMutex.Unlock()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
appsMutex.Unlock()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "删除成功"})
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "删除成功"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="appList"></div>
|
<div id="appList"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer>
|
||||||
|
<p>App分发系统By拉面王</p>
|
||||||
|
<p>当前发布版本:V0.0.3|||仅供内部使用,严禁分发</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 引入外部JavaScript文件,实现交互功能 -->
|
<!-- 引入外部JavaScript文件,实现交互功能 -->
|
||||||
|
|||||||
34
script.js
34
script.js
@@ -130,6 +130,9 @@ async function loadAppList() {
|
|||||||
const appDate = document.createElement('p'); // 创建App日期元素
|
const appDate = document.createElement('p'); // 创建App日期元素
|
||||||
appDate.textContent = `上传时间:${app.date}`;
|
appDate.textContent = `上传时间:${app.date}`;
|
||||||
|
|
||||||
|
const appId = document.createElement('p'); // 创建App ID元素
|
||||||
|
appId.textContent = `AppID:${app.id}`;
|
||||||
|
|
||||||
const downloadBtn = document.createElement('button'); // 创建下载按钮
|
const downloadBtn = document.createElement('button'); // 创建下载按钮
|
||||||
downloadBtn.className = 'download-btn';
|
downloadBtn.className = 'download-btn';
|
||||||
downloadBtn.textContent = '下载';
|
downloadBtn.textContent = '下载';
|
||||||
@@ -148,6 +151,7 @@ async function loadAppList() {
|
|||||||
// 组装App项
|
// 组装App项
|
||||||
appInfo.appendChild(appName);
|
appInfo.appendChild(appName);
|
||||||
appInfo.appendChild(appDate);
|
appInfo.appendChild(appDate);
|
||||||
|
appInfo.appendChild(appId);
|
||||||
appItem.appendChild(appInfo);
|
appItem.appendChild(appInfo);
|
||||||
|
|
||||||
// 创建按钮容器并添加按钮
|
// 创建按钮容器并添加按钮
|
||||||
@@ -294,7 +298,35 @@ function copyDirectLink(appId, fileName) {
|
|||||||
const directLink = `${API_BASE_URL}/apps/${appId}`; // 生成直链URL
|
const directLink = `${API_BASE_URL}/apps/${appId}`; // 生成直链URL
|
||||||
|
|
||||||
// 复制到剪贴板
|
// 复制到剪贴板
|
||||||
navigator.clipboard.writeText(directLink)
|
const copyToClipboard = (text) => {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
return navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
textarea.style.opacity = '0';
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const successful = document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
if (successful) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error('复制命令执行失败'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
copyToClipboard(directLink)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 显示复制成功提示
|
// 显示复制成功提示
|
||||||
const message = document.createElement('div');
|
const message = document.createElement('div');
|
||||||
|
|||||||
10
style.css
10
style.css
@@ -24,6 +24,15 @@ header {
|
|||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
border-bottom: 1px solid #000;
|
border-bottom: 1px solid #000;
|
||||||
}
|
}
|
||||||
|
/* 页面尾部样式 */
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
/* padding-bottom: 20px; */
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
/* 页面标题样式 */
|
/* 页面标题样式 */
|
||||||
header h1 {
|
header h1 {
|
||||||
@@ -92,6 +101,7 @@ p {
|
|||||||
/* 文档项内段落样式 */
|
/* 文档项内段落样式 */
|
||||||
.doc-item p{
|
.doc-item p{
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
max-width: 706px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user