初始化仓库,前端完成
This commit is contained in:
38
.trae/documents/App分发系统优化.md
Normal file
38
.trae/documents/App分发系统优化.md
Normal file
@@ -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跨域请求
|
||||||
|
- 支持文件上传和下载
|
||||||
|
- 简单易用,便于快速实现
|
||||||
181
README.md
Normal file
181
README.md
Normal file
@@ -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<br>`name`: App名称(可选,不填则使用文件名作为默认值)<br>`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
|
||||||
41
index.html
Normal file
41
index.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>极简App分发系统</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>App分发系统</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 登录区域 -->
|
||||||
|
<div id="loginSection" class="section">
|
||||||
|
<h2>管理员登录</h2>
|
||||||
|
<input type="password" id="password" placeholder="请输入密码" maxlength="20">
|
||||||
|
<button onclick="login()">登录</button>
|
||||||
|
<p id="loginMessage"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传区域 -->
|
||||||
|
<div id="uploadSection" class="section" style="display: none;">
|
||||||
|
<h2>上传App</h2>
|
||||||
|
<input type="text" id="appName" placeholder="App名称" maxlength="50">
|
||||||
|
<input type="file" id="appFile" accept=".apk,.ipa,.zip">
|
||||||
|
<button onclick="uploadApp()">上传</button>
|
||||||
|
<p id="uploadMessage"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 下载区域 -->
|
||||||
|
<div id="downloadSection" class="section">
|
||||||
|
<h2>可用App</h2>
|
||||||
|
<div id="appList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
209
script.js
Normal file
209
script.js
Normal file
@@ -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 = '<p style="text-align: center;">暂无App</p>';
|
||||||
|
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 = '<p style="color: #ff0000; text-align: center;">获取App列表失败</p>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传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;
|
||||||
111
style.css
Normal file
111
style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user