commit d44634798131450f6de231ffe4a27d1bf27bb511 Author: sakurai Date: Tue Dec 23 22:19:02 2025 +0800 初始化仓库,前端完成 diff --git a/.trae/documents/App分发系统优化.md b/.trae/documents/App分发系统优化.md new file mode 100644 index 0000000..6c59e21 --- /dev/null +++ b/.trae/documents/App分发系统优化.md @@ -0,0 +1,38 @@ +## 优化计划 + +### 1. CSS样式简化 +- 删除所有装饰性样式(颜色、背景、阴影、圆角、过渡等) +- 只保留基本布局和定位 +- 保持所有CSS选择器不变,方便后续手动设计 + +### 2. 添加后端API调用 +- 在JavaScript中添加后端API基础URL配置 +- 将localStorage存储替换为后端API调用 +- 修改以下函数: + - `getAppsFromStorage()` → 调用 GET /api/apps + - `saveAppsToStorage()` → 调用 POST /api/apps + - `uploadApp()` → 调用 POST /api/apps 上传文件 + +### 3. 后端API设计 +- 端口:3000 +- 无需鉴权 +- 接口列表: + - `GET /api/apps` - 获取App列表 + - `POST /api/apps` - 上传App(支持multipart/form-data) + - `GET /api/apps/:id` - 下载指定App + +### 4. 前端修改要点 +- 保持现有登录逻辑不变 +- 保持所有HTML结构不变 +- 保持所有CSS选择器不变 +- 只修改JavaScript中的数据存储逻辑 + +### 5. 文件修改清单 +- `style.css` - 简化样式 +- `script.js` - 添加后端API调用 + +### 6. 技术要求 +- 后端API采用RESTful设计 +- 支持CORS跨域请求 +- 支持文件上传和下载 +- 简单易用,便于快速实现 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9da62c1 --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# 极简App分发系统 + +一个纯前端实现的App分发系统,支持管理员登录、App上传和无需登录下载功能。 + +## 功能特性 + +- ✅ 管理员登录(密码:admin123) +- ✅ App上传(支持.apk、.ipa、.zip文件,最大100MB) +- ✅ 无需登录即可下载 +- ✅ 响应式设计 +- ✅ 纯前端实现,无框架依赖 +- ✅ 支持后端API扩展 + +## 快速开始 + +### 1. 启动前端服务 + +```bash +# 使用Python启动简单HTTP服务器 +python -m http.server 8000 + +# 或使用Node.js +npx http-server -p 8000 +``` + +访问地址:http://localhost:8000 + +### 2. 后端API实现(可选) + +如果需要持久化存储和多用户访问,可以实现后端API。 + +#### 后端要求 + +- 端口:3000 +- 无需鉴权 +- 支持CORS跨域请求 + +#### API接口 + +| 方法 | 路径 | 描述 | 请求体 | 响应 | +|------|------|------|--------|------| +| GET | /api/apps | 获取App列表 | - | `[{id: string, name: string, fileName: string, date: string}]` | +| POST | /api/apps | 上传App | multipart/form-data
`name`: App名称(可选,不填则使用文件名作为默认值)
`file`: 文件 | `{success: boolean, message?: string}` | +| GET | /api/apps/:id | 下载指定App | - | 文件流 | + +#### 后端实现示例(Node.js + Express) + +```javascript +const express = require('express'); +const multer = require('multer'); +const cors = require('cors'); + +const app = express(); +const upload = multer({ dest: 'uploads/' }); + +app.use(cors()); + +// 模拟数据存储 +let apps = []; + +// 获取App列表 +app.get('/api/apps', (req, res) => { + res.json(apps); +}); + +// 上传App +app.post('/api/apps', upload.single('file'), (req, res) => { + let { name } = req.body; + const file = req.file; + + if (!file) { + return res.json({ success: false, message: '缺少文件' }); + } + + // 如果name为空,使用文件名作为默认值(去除扩展名) + if (!name || !name.trim()) { + name = file.originalname.replace(/\.[^/.]+$/, ''); + } + + const app = { + id: Date.now().toString(), + name, + fileName: file.originalname, + date: new Date().toLocaleString('zh-CN') + }; + + apps.unshift(app); + res.json({ success: true }); +}); + +// 下载App +app.get('/api/apps/:id', (req, res) => { + const app = apps.find(a => a.id === req.params.id); + if (!app) { + return res.status(404).json({ success: false, message: 'App不存在' }); + } + + // 实际实现中,这里应该返回真实的文件流 + res.send('文件下载功能待实现'); +}); + +app.listen(3000, () => { + console.log('Server running on port 3000'); +}); +``` + +## 技术栈 + +- HTML5 +- CSS3 +- JavaScript (ES6+) + +## 文件结构 + +``` +├── index.html # 主页面 +├── style.css # 样式文件 +├── script.js # 核心逻辑 +└── README.md # 说明文档 +``` + +## 使用说明 + +### 管理员登录 + +1. 在登录框中输入密码:admin123 +2. 点击"登录"按钮 +3. 成功后将显示上传区域 + +### 上传App + +1. 登录成功后,在上传区域填写App名称(可选,不填写则使用文件名作为默认值) +2. 选择要上传的文件(.apk、.ipa、.zip) +3. 点击"上传"按钮 +4. 上传成功后,App将显示在下载列表中 + +### 下载App + +1. 在下载区域找到要下载的App +2. 点击"下载"按钮即可开始下载 +3. 点击"复制直链"按钮可以复制App的直接下载链接 + +## 自定义配置 + +### 修改管理员密码 + +在 `script.js` 中修改: + +```javascript +const ADMIN_PASSWORD = 'your_new_password'; +``` + +### 修改后端API地址 + +在 `script.js` 中修改: + +```javascript +const API_BASE_URL = 'http://your-backend-url:port/api'; +``` + +### 自定义样式 + +CSS选择器保持不变,可以直接修改 `style.css` 文件来自定义样式。 + +## 浏览器兼容性 + +- Chrome 60+ +- Firefox 55+ +- Safari 12+ +- Edge 79+ + +## 注意事项 + +1. 默认使用localStorage存储,数据只保存在浏览器中 +2. 建议部署后端API以实现持久化存储 +3. 生产环境中请修改默认密码 +4. 建议添加HTTPS支持,尤其是在公网环境中 + +## 许可证 + +MIT \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..8b50442 --- /dev/null +++ b/index.html @@ -0,0 +1,41 @@ + + + + + + 极简App分发系统 + + + +
+
+

App分发系统

+
+ + +
+

管理员登录

+ + +

+
+ + + + + +
+

可用App

+
+
+
+ + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..7e0a63a --- /dev/null +++ b/script.js @@ -0,0 +1,209 @@ +// 配置 +const ADMIN_PASSWORD = 'admin123'; // 管理员密码 +const API_BASE_URL = 'http://localhost:3000/api'; // 后端API基础URL + +// 初始化 +function init() { + checkLoginStatus(); + loadAppList(); +} + +// 检查登录状态 +function checkLoginStatus() { + const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; + if (isLoggedIn) { + showUploadSection(); + } +} + +// 登录功能 +function login() { + const password = document.getElementById('password').value; + const message = document.getElementById('loginMessage'); + + if (password === ADMIN_PASSWORD) { + localStorage.setItem('isLoggedIn', 'true'); + showUploadSection(); + message.textContent = '登录成功!'; + message.style.color = '#0000ff'; + document.getElementById('password').value = ''; + } else { + message.textContent = '密码错误,请重试!'; + message.style.color = '#ff0000'; + } +} + +// 显示上传区域 +function showUploadSection() { + document.getElementById('loginSection').style.display = 'none'; + document.getElementById('uploadSection').style.display = 'block'; + const loginSection = document.getElementById('loginSection'); + const logoutBtn = document.createElement('button'); + logoutBtn.textContent = '退出登录'; + logoutBtn.className = 'logout-btn'; + logoutBtn.onclick = logout; + loginSection.appendChild(logoutBtn); +} + +// 退出登录 +function logout() { + localStorage.removeItem('isLoggedIn'); + location.reload(); +} + +// 加载App列表 +function loadAppList() { + const appList = document.getElementById('appList'); + + fetch(`${API_BASE_URL}/apps`) + .then(response => response.json()) + .then(apps => { + if (apps.length === 0) { + appList.innerHTML = '

暂无App

'; + return; + } + + appList.innerHTML = ''; + apps.forEach(app => { + const appItem = document.createElement('div'); + appItem.className = 'app-item'; + + const appInfo = document.createElement('div'); + appInfo.className = 'app-info'; + + const appName = document.createElement('h3'); + appName.textContent = app.name; + + const appDate = document.createElement('p'); + appDate.textContent = `上传时间:${app.date}`; + + const downloadBtn = document.createElement('button'); + downloadBtn.className = 'download-btn'; + downloadBtn.textContent = '下载'; + downloadBtn.onclick = () => downloadApp(app.id, app.fileName); + + const copyLinkBtn = document.createElement('button'); + copyLinkBtn.className = 'download-btn'; + copyLinkBtn.textContent = '复制直链'; + copyLinkBtn.onclick = () => copyDirectLink(app.id, app.fileName); + + appInfo.appendChild(appName); + appInfo.appendChild(appDate); + appItem.appendChild(appInfo); + + const buttonsContainer = document.createElement('div'); + buttonsContainer.style.display = 'flex'; + buttonsContainer.style.gap = '10px'; + buttonsContainer.appendChild(downloadBtn); + buttonsContainer.appendChild(copyLinkBtn); + + appItem.appendChild(buttonsContainer); + appList.appendChild(appItem); + }); + }) + .catch(error => { + console.error('获取App列表失败:', error); + appList.innerHTML = '

获取App列表失败

'; + }); +} + +// 上传App +function uploadApp() { + let appName = document.getElementById('appName').value; + const appFile = document.getElementById('appFile').files[0]; + const message = document.getElementById('uploadMessage'); + + if (!appFile) { + message.textContent = '请选择文件!'; + message.style.color = '#ff0000'; + return; + } + + if (appFile.size > 100 * 1024 * 1024) { // 限制100MB + message.textContent = '文件大小不能超过100MB!'; + message.style.color = '#ff0000'; + return; + } + + // 如果App名称为空,使用文件名作为默认值 + if (!appName.trim()) { + // 去除文件扩展名 + appName = appFile.name.replace(/\.[^/.]+$/, ''); + } + + const formData = new FormData(); + formData.append('name', appName); + formData.append('file', appFile); + + fetch(`${API_BASE_URL}/apps`, { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(result => { + if (result.success) { + message.textContent = '上传成功!'; + message.style.color = '#0000ff'; + loadAppList(); + // 清空表单 + document.getElementById('appName').value = ''; + document.getElementById('appFile').value = ''; + } else { + message.textContent = '上传失败:' + (result.message || '未知错误'); + message.style.color = '#ff0000'; + } + }) + .catch(error => { + console.error('上传失败:', error); + message.textContent = '上传失败,请重试!'; + message.style.color = '#ff0000'; + }); +} + +// 下载App +function downloadApp(appId, fileName) { + const link = document.createElement('a'); + link.href = `${API_BASE_URL}/apps/${appId}`; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + +// 复制直链 +function copyDirectLink(appId, fileName) { + const directLink = `${API_BASE_URL}/apps/${appId}`; + + // 复制到剪贴板 + navigator.clipboard.writeText(directLink) + .then(() => { + // 显示复制成功提示 + const message = document.createElement('div'); + message.textContent = '直链已复制到剪贴板!'; + message.style.cssText = ` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #000; + color: #fff; + padding: 10px 20px; + border-radius: 5px; + font-size: 14px; + z-index: 1000; + `; + + document.body.appendChild(message); + + // 3秒后移除提示 + setTimeout(() => { + document.body.removeChild(message); + }, 3000); + }) + .catch(err => { + console.error('复制失败:', err); + }); +} + +// 页面加载完成后初始化 +window.onload = init; \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..9c764c3 --- /dev/null +++ b/style.css @@ -0,0 +1,111 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; +} + +.container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +header { + text-align: center; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 1px solid #000; +} + +header h1 { + font-size: 24px; +} + +.section { + margin-bottom: 30px; + padding: 20px; + border: 1px solid #000; +} + +.section h2 { + font-size: 18px; + margin-bottom: 15px; +} + +input { + display: block; + width: 100%; + padding: 10px; + margin-bottom: 10px; + border: 1px solid #000; + font-size: 14px; +} + +button { + border: 1px solid #000; + padding: 10px 20px; + cursor: pointer; + font-size: 14px; +} + +p { + margin-top: 10px; + font-size: 14px; +} + +.app-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; + margin-bottom: 10px; + border: 1px solid #000; +} + +.app-info { + flex: 1; +} + +.app-info h3 { + font-size: 16px; + margin-bottom: 5px; +} + +.app-info p { + font-size: 12px; + margin: 0; +} + +.download-btn { + padding: 8px 15px; + font-size: 12px; +} + +.logout-btn { + margin-top: 10px; + font-size: 12px; +} + +@media (max-width: 600px) { + .container { + margin: 10px; + padding: 15px; + } + + .section { + padding: 15px; + } + + .app-item { + flex-direction: column; + align-items: flex-start; + } + + .download-btn { + margin-top: 10px; + } +} \ No newline at end of file