Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b2b158e73 | |||
| 136728798a | |||
| b733f81ed8 | |||
| 48caa4168c | |||
| 04c0c3cae5 |
51
README.md
51
README.md
@@ -5,7 +5,8 @@
|
||||
## 功能特性
|
||||
|
||||
- ✅ 管理员登录(密码: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
|
||||
# 使用Python启动简单HTTP服务器
|
||||
@@ -27,7 +37,7 @@ npx http-server -p 8000
|
||||
|
||||
#### 后端要求
|
||||
|
||||
- 端口:3000
|
||||
- 端口:6903 (修改自默认的3000端口)
|
||||
- 无需鉴权
|
||||
- 支持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}` |
|
||||
| DELETE | /api/apps/:id | 删除指定App | - | `{success: boolean, message?: string}` |
|
||||
| GET | /api/apps/:id | 下载指定App | - | 文件流 |
|
||||
| GET | /api/docs | 获取文档内容 | - | `[{docBody: string}]` |
|
||||
|
||||
## 技术栈
|
||||
|
||||
- HTML5
|
||||
- CSS3
|
||||
- JavaScript (ES6+)
|
||||
- Go (后端)
|
||||
- Gin (Web框架)
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
├── background/ # 后端目录
|
||||
│ ├── main.go # 后端主程序
|
||||
│ ├── apps.json # 应用数据持久化文件
|
||||
│ ├── files/ # 存储上传的文件
|
||||
│ └── docs/ # 存储文档文件
|
||||
├── index.html # 主页面
|
||||
├── style.css # 样式文件
|
||||
├── script.js # 核心逻辑
|
||||
@@ -74,6 +93,12 @@ npx http-server -p 8000
|
||||
1. 在下载区域找到要下载的App
|
||||
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';
|
||||
```
|
||||
|
||||
### 修改后端端口
|
||||
|
||||
在 `background/main.go` 中修改:
|
||||
|
||||
```go
|
||||
port = ":6903" // 修改为你想要的端口
|
||||
```
|
||||
|
||||
## 数据持久化
|
||||
|
||||
- 应用列表数据保存在 `background/apps.json` 文件中
|
||||
- 上传的文件保存在 `background/files/` 目录中
|
||||
- 文档内容保存在 `background/docs/` 目录中
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 默认使用localStorage存储,数据只保存在浏览器中
|
||||
2. 建议部署后端API以实现持久化存储
|
||||
1. 默认使用localStorage存储登录状态,数据只保存在浏览器中
|
||||
2. 应用数据持久化在后端的apps.json文件中
|
||||
3. 生产环境中请修改默认密码
|
||||
4. 建议添加HTTPS支持,尤其是在公网环境中
|
||||
|
||||
|
||||
@@ -70,11 +70,8 @@ func loadApps() {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存apps数据
|
||||
// 保存apps数据(调用者需要持有锁)
|
||||
func saveApps() error {
|
||||
appsMutex.Lock()
|
||||
defer appsMutex.Unlock()
|
||||
|
||||
file, err := os.Create(jsonFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create apps.json: %w", err)
|
||||
@@ -129,7 +126,7 @@ func main() {
|
||||
{
|
||||
api.GET("/apps", getApps)
|
||||
api.POST("/apps", uploadApp)
|
||||
api.DELETE("/apps/:id", deleteApp) // 添加删除应用的API端点
|
||||
api.DELETE("/apps/:id", deleteApp) // 添加删除应用的API端点
|
||||
api.GET("/apps/:id", downloadApp)
|
||||
api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL
|
||||
api.GET("/docs", getDocs) // 新增获取文档的API端点
|
||||
@@ -223,13 +220,13 @@ func uploadApp(c *gin.Context) {
|
||||
|
||||
appsMutex.Lock()
|
||||
apps = append(apps, newApp)
|
||||
appsMutex.Unlock()
|
||||
|
||||
// 保存到文件
|
||||
if err := saveApps(); err != nil {
|
||||
appsMutex.Unlock()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
appsMutex.Unlock()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
@@ -308,13 +305,13 @@ func deleteApp(c *gin.Context) {
|
||||
// 从apps切片中删除该应用
|
||||
appsMutex.Lock()
|
||||
apps = append(apps[:targetIndex], apps[targetIndex+1:]...)
|
||||
appsMutex.Unlock()
|
||||
|
||||
// 保存到文件
|
||||
if err := saveApps(); err != nil {
|
||||
appsMutex.Unlock()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
appsMutex.Unlock()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "删除成功"})
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
</div>
|
||||
<div id="appList"></div>
|
||||
</div>
|
||||
<footer>
|
||||
<p>App分发系统By拉面王</p>
|
||||
<p>当前发布版本:V0.0.3|||仅供内部使用,严禁分发</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- 引入外部JavaScript文件,实现交互功能 -->
|
||||
|
||||
34
script.js
34
script.js
@@ -130,6 +130,9 @@ async function loadAppList() {
|
||||
const appDate = document.createElement('p'); // 创建App日期元素
|
||||
appDate.textContent = `上传时间:${app.date}`;
|
||||
|
||||
const appId = document.createElement('p'); // 创建App ID元素
|
||||
appId.textContent = `AppID:${app.id}`;
|
||||
|
||||
const downloadBtn = document.createElement('button'); // 创建下载按钮
|
||||
downloadBtn.className = 'download-btn';
|
||||
downloadBtn.textContent = '下载';
|
||||
@@ -148,6 +151,7 @@ async function loadAppList() {
|
||||
// 组装App项
|
||||
appInfo.appendChild(appName);
|
||||
appInfo.appendChild(appDate);
|
||||
appInfo.appendChild(appId);
|
||||
appItem.appendChild(appInfo);
|
||||
|
||||
// 创建按钮容器并添加按钮
|
||||
@@ -294,7 +298,35 @@ function copyDirectLink(appId, fileName) {
|
||||
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(() => {
|
||||
// 显示复制成功提示
|
||||
const message = document.createElement('div');
|
||||
|
||||
10
style.css
10
style.css
@@ -24,6 +24,15 @@ header {
|
||||
padding-bottom: 20px;
|
||||
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 {
|
||||
@@ -92,6 +101,7 @@ p {
|
||||
/* 文档项内段落样式 */
|
||||
.doc-item p{
|
||||
margin-top: 0;
|
||||
max-width: 706px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user