5 Commits
v0.0.2 ... main

Author SHA1 Message Date
1b2b158e73 fix: 修复issue,COPY-01 复制直链功能 FAILED 控制台报错:Cannot read properties of undefined (reading 'writeText') 剪贴板API兼容性问题、改进剪贴板复制功能以兼容旧浏览器
添加对旧浏览器的兼容性处理,当navigator.clipboard不可用时,使用document.execCommand作为备选方案
2026-01-07 19:47:42 +08:00
136728798a style: 限制文档项段落的最大宽度 2026-01-07 14:22:22 +08:00
b733f81ed8 fix(并发控制): 修复apps数据保存时的并发安全问题
将saveApps函数的锁管理移到调用方,确保在文件保存操作期间保持锁
在uploadApp和deleteApp中调整锁的释放时机,防止数据竞争
2026-01-07 14:03:45 +08:00
48caa4168c feat: 添加页脚和AppID显示
在页面底部添加包含版本信息的页脚,并在应用列表中显示AppID
2026-01-07 13:56:50 +08:00
04c0c3cae5 docs: 更新readme.md 2026-01-07 00:29:05 +08:00
5 changed files with 98 additions and 16 deletions

View File

@@ -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支持尤其是在公网环境中

View File

@@ -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)
@@ -129,7 +126,7 @@ func main() {
{ {
api.GET("/apps", getApps) api.GET("/apps", getApps)
api.POST("/apps", uploadApp) 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)
api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL
api.GET("/docs", getDocs) // 新增获取文档的API端点 api.GET("/docs", getDocs) // 新增获取文档的API端点
@@ -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": "删除成功"})
} }

View File

@@ -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文件实现交互功能 -->

View File

@@ -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');

View File

@@ -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;
} }