diff --git a/.trae/documents/plan_20251224_165001.md b/.trae/documents/plan_20251224_165001.md new file mode 100644 index 0000000..4179bb9 --- /dev/null +++ b/.trae/documents/plan_20251224_165001.md @@ -0,0 +1,108 @@ +## 前端生产级健壮性提升计划 + +### 1. 核心改进点 + +#### 添加刷新按钮 + +* 在下载区域标题旁添加刷新按钮 + +* 点击按钮重新加载App列表 + +* 保持与现有按钮风格一致 + +#### 增强API调用健壮性 + +* 统一API请求处理,添加错误处理 + +* 完善各种错误场景的用户反馈 + +* 添加加载状态提示 + +* 处理网络错误和超时情况 + +#### 改进用户体验 + +* 添加操作成功/失败的视觉反馈 + +* 优化表单验证 + +* 改进文件上传反馈 + +#### 前后端适配协调 + +* 确保前端请求与后端API完全匹配 + +* 处理后端返回的各种响应状态 + +* 优化数据处理逻辑 + +### 2. 实现细节 + +#### 文件修改 + +1. **index.html** + + * 在下载区域标题旁添加刷新按钮 + + * 添加加载状态指示器 + +2. **script.js** + + * 添加`refreshAppList()`函数 + + * 统一API请求处理函数 + + * 完善错误处理逻辑 + + * 添加加载状态管理 + + * 优化现有函数,确保与后端API适配 + +3. **style.css** + + * 微调样式以适应刷新按钮 + + * 添加加载状态样式 + +#### 关键功能实现 + +* **刷新功能**:点击按钮调用`loadAppList()`重新加载App列表 + +* **API健壮性**:添加try-catch、错误状态码处理、超时处理 + +* **用户反馈**:添加加载中提示、操作结果反馈 + +* **表单验证**:增强对输入的验证,防止无效请求 + +### 3. 保持极简风格 + +* 不添加复杂UI元素 + +* 保持现有配色和布局 + +* 使用原生JavaScript实现 + +* 不引入额外库 + +### 4. 预期效果 + +* 前端与后端API适配更协调 + +* 提供更好的用户反馈和错误处理 + +* 添加刷新功能方便用户手动更新App列表 + +* 提升系统整体健壮性和用户体验 + +### 5. 测试要点 + +* 刷新功能正常工作 + +* API请求错误处理 + +* 网络异常情况处理 + +* 各种边界情况测试 + +* 保持极简风格不变 + diff --git a/index.html b/index.html index 22fbba1..73dface 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,23 @@ + App分发系统 + +
+

App分发系统By拉面王

项目经理:Qwen3max,测试经理:Doubao-seed-1.8,开发经理:Doubao-seed-1.8,bug修复和模块整合:拉面王
- +

文档

@@ -22,7 +26,7 @@
- +

管理员登录

@@ -30,7 +34,7 @@

- + - +

可用App

@@ -52,6 +56,7 @@
+ \ No newline at end of file diff --git a/script.js b/script.js index 49aa520..894420f 100644 --- a/script.js +++ b/script.js @@ -1,147 +1,156 @@ -// 配置 +// 配置常量定义 const ADMIN_PASSWORD = 'admin123'; // 管理员密码 const API_BASE_URL = 'http://localhost:6903/api'; // 后端API基础URL -// 初始化 +// 初始化函数,页面加载完成后执行 function init() { - checkLoginStatus(); - loadAppList(); - getDoc(); + checkLoginStatus(); // 检查登录状态 + loadAppList(); // 加载App列表 + getDoc(); // 获取文档内容 } -// 检查登录状态 +// 检查登录状态函数 function checkLoginStatus() { + // 从本地存储获取登录状态 const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; if (isLoggedIn) { - showUploadSection(); + showUploadSection(); // 如果已登录,显示上传区域 } } -// 登录功能 +// 登录功能函数 function login() { - const password = document.getElementById('password').value; - const message = document.getElementById('loginMessage'); + const password = document.getElementById('password').value; // 获取输入的密码 + const message = document.getElementById('loginMessage'); // 获取登录消息元素 + // 验证密码是否为空 if (!password.trim()) { message.textContent = '密码不能为空!'; message.style.color = '#ff0000'; return; } + // 验证密码是否正确 if (password === ADMIN_PASSWORD) { - localStorage.setItem('isLoggedIn', 'true'); - showUploadSection(); + localStorage.setItem('isLoggedIn', 'true'); // 保存登录状态到本地存储 + showUploadSection(); // 显示上传区域 message.textContent = '登录成功!'; message.style.color = '#008000'; - document.getElementById('password').value = ''; + 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'; + document.getElementById('loginSection').style.display = 'none'; // 隐藏登录区域 + document.getElementById('uploadSection').style.display = 'block'; // 显示上传区域 const loginSection = document.getElementById('loginSection'); // 检查是否已经添加了退出按钮,避免重复添加 if (!loginSection.querySelector('.logout-btn')) { - const logoutBtn = document.createElement('button'); + const logoutBtn = document.createElement('button'); // 创建退出按钮 logoutBtn.textContent = '退出登录'; logoutBtn.className = 'logout-btn'; - logoutBtn.onclick = logout; - loginSection.appendChild(logoutBtn); + logoutBtn.onclick = logout; // 绑定退出登录事件 + loginSection.appendChild(logoutBtn); // 添加按钮到登录区域 } } -// 退出登录 +// 退出登录函数 function logout() { - localStorage.removeItem('isLoggedIn'); - location.reload(); + localStorage.removeItem('isLoggedIn'); // 清除本地存储中的登录状态 + location.reload(); // 刷新页面 } -// 显示加载状态 +// 显示加载状态函数 function showLoading() { const loadingIndicator = document.getElementById('loadingIndicator'); if (loadingIndicator) { - loadingIndicator.style.display = 'inline'; + loadingIndicator.style.display = 'inline'; // 显示加载指示器 } } -// 隐藏加载状态 +// 隐藏加载状态函数 function hideLoading() { const loadingIndicator = document.getElementById('loadingIndicator'); if (loadingIndicator) { - loadingIndicator.style.display = 'none'; + loadingIndicator.style.display = 'none'; // 隐藏加载指示器 } } -// 刷新App列表 +// 刷新App列表函数 function refreshAppList() { - loadAppList(); + loadAppList(); // 调用加载App列表函数 } -// 加载App列表 +// 加载App列表函数 async function loadAppList() { - const appList = document.getElementById('appList'); + const appList = document.getElementById('appList'); // 获取App列表容器 try { - showLoading(); + showLoading(); // 显示加载状态 + // 发送GET请求获取App列表 const response = await fetch(`${API_BASE_URL}/apps`, { method: 'GET', headers: { 'Accept': 'application/json' }, - timeout: 10000 // 10秒超时 + timeout: 10000 // 10秒超时设置 }); + // 检查响应状态 if (!response.ok) { throw new Error(`HTTP错误!状态:${response.status}`); } - const apps = await response.json(); + const apps = await response.json(); // 解析响应数据 + // 处理空列表情况 if (apps.length === 0) { appList.innerHTML = '

暂无App

'; return; } + // 清空列表并重新渲染 appList.innerHTML = ''; apps.forEach(app => { - const appItem = document.createElement('div'); + const appItem = document.createElement('div'); // 创建App项元素 appItem.className = 'app-item'; - const appInfo = document.createElement('div'); + const appInfo = document.createElement('div'); // 创建App信息容器 appInfo.className = 'app-info'; - const appName = document.createElement('h3'); + const appName = document.createElement('h3'); // 创建App名称元素 appName.textContent = app.name; - const appDate = document.createElement('p'); + const appDate = document.createElement('p'); // 创建App日期元素 appDate.textContent = `上传时间:${app.date}`; - const downloadBtn = document.createElement('button'); + const downloadBtn = document.createElement('button'); // 创建下载按钮 downloadBtn.className = 'download-btn'; downloadBtn.textContent = '下载'; downloadBtn.onclick = () => downloadApp(app.id, app.fileName); - const deleteBtn = document.createElement('button'); + const deleteBtn = document.createElement('button'); // 创建删除按钮 deleteBtn.className = 'download-btn'; deleteBtn.textContent = '删除'; deleteBtn.onclick = () => deleteApp(app.id); - const copyLinkBtn = document.createElement('button'); + const copyLinkBtn = document.createElement('button'); // 创建复制直链按钮 copyLinkBtn.className = 'download-btn'; copyLinkBtn.textContent = '复制直链'; copyLinkBtn.onclick = () => copyDirectLink(app.id, app.fileName); + // 组装App项 appInfo.appendChild(appName); appInfo.appendChild(appDate); appItem.appendChild(appInfo); + // 创建按钮容器并添加按钮 const buttonsContainer = document.createElement('div'); buttonsContainer.style.display = 'flex'; buttonsContainer.style.gap = '10px'; @@ -156,22 +165,24 @@ async function loadAppList() { console.error('获取App列表失败:', error); appList.innerHTML = `

获取App列表失败:${error.message}

`; } finally { - hideLoading(); + hideLoading(); // 无论成功失败,都隐藏加载状态 } } -// 上传App +// 上传App函数 async function uploadApp() { - let appName = document.getElementById('appName').value; - const appFile = document.getElementById('appFile').files[0]; - const message = document.getElementById('uploadMessage'); + let appName = document.getElementById('appName').value; // 获取App名称 + const appFile = document.getElementById('appFile').files[0]; // 获取选择的文件 + const message = document.getElementById('uploadMessage'); // 获取上传消息元素 + // 验证文件是否选择 if (!appFile) { message.textContent = '请选择文件!'; message.style.color = '#ff0000'; return; } + // 验证文件大小(限制1GB) if (appFile.size > 100 * 1024 * 1024 * 1024) { // 限制1000MB message.textContent = '文件大小不能超过1GB!'; message.style.color = '#ff0000'; @@ -184,6 +195,7 @@ async function uploadApp() { appName = appFile.name.replace(/\.[^/.]+$/, ''); } + // 创建FormData对象,用于发送文件 const formData = new FormData(); formData.append('name', appName); formData.append('file', appFile); @@ -192,22 +204,25 @@ async function uploadApp() { message.textContent = '上传中...'; message.style.color = '#0000ff'; + // 发送POST请求上传文件 const response = await fetch(`${API_BASE_URL}/apps`, { method: 'POST', body: formData, - timeout: 30000 // 30秒超时 + timeout: 30000 // 30秒超时设置 }); + // 检查响应状态 if (!response.ok) { throw new Error(`HTTP错误!状态:${response.status}`); } - const result = await response.json(); + const result = await response.json(); // 解析响应数据 + // 处理上传结果 if (result.success) { message.textContent = '上传成功!'; message.style.color = '#008000'; - loadAppList(); + loadAppList(); // 重新加载App列表 // 清空表单 document.getElementById('appName').value = ''; document.getElementById('appFile').value = ''; @@ -222,16 +237,18 @@ async function uploadApp() { } } -// 删除App +// 删除App函数 function deleteApp(appId) { + // 显示确认对话框,包含特殊问题验证 if (!confirm('如果确定删除此APP,请将矩阵 A = [[3,1,0],[1,2,1],[0,1,1]]的特征值、特征向量,并验证A是正定矩阵的解题步骤做出来,然后我也没做')) { return; } } -// 下载App +// 下载App函数 function downloadApp(appId, fileName) { try { + // 创建下载链接并触发点击 const link = document.createElement('a'); link.href = `${API_BASE_URL}/apps/${appId}`; link.download = fileName; @@ -244,9 +261,9 @@ function downloadApp(appId, fileName) { } } -// 复制直链 +// 复制直链函数 function copyDirectLink(appId, fileName) { - const directLink = `${API_BASE_URL}/apps/${appId}`; + const directLink = `${API_BASE_URL}/apps/${appId}`; // 生成直链URL // 复制到剪贴板 navigator.clipboard.writeText(directLink) @@ -280,39 +297,45 @@ function copyDirectLink(appId, fileName) { }); } +// 获取文档内容的API调用函数 async function getDocApi() { try { - showLoading(); + showLoading(); // 显示加载状态 - const docContent = document.getElementById('docContent'); - docContent.innerHTML = ``; + const docContent = document.getElementById('docContent'); // 获取文档内容容器 + docContent.innerHTML = ``; // 清空现有内容 + // 发送GET请求获取文档 const response = await fetch(`${API_BASE_URL}/docs`, { method: 'GET', headers: { 'Accept': 'application/json' }, - timeout: 10000 // 10秒超时 + timeout: 10000 // 10秒超时设置 }); + // 检查响应状态 if (!response.ok) { throw new Error(`HTTP错误!状态:${response.status}`); } - const docs = await response.json(); + const docs = await response.json(); // 解析响应数据 + // 处理空文档情况 if (docs.length === 0) { docContent.innerHTML = '

暂无文档

'; return; } + + // 渲染文档列表 for (let i = 0; i < docs.length; i++) { const doc = docs[i]; - const docItem = document.createElement('div'); + const docItem = document.createElement('div'); // 创建文档项元素 docItem.className = 'doc-item'; docItem.innerHTML = `

${doc.docBody}

`; - docContent.appendChild(docItem); + docContent.appendChild(docItem); // 添加到文档容器 } } @@ -322,9 +345,10 @@ async function getDocApi() { return; } } -// 获取文档内容 + +// 获取文档内容的入口函数 function getDoc() { - getDocApi(); + getDocApi(); // 调用API获取文档 } // 页面加载完成后初始化 diff --git a/style.css b/style.css index ebf1ac2..4e4a327 100644 --- a/style.css +++ b/style.css @@ -1,19 +1,23 @@ +/* 全局样式重置 */ * { 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; @@ -21,21 +25,25 @@ header { 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%; @@ -45,6 +53,7 @@ input { font-size: 14px; } +/* 按钮通用样式 */ button { border: 1px solid #000; padding: 10px 20px; @@ -54,19 +63,23 @@ button { transition: background-color 0.2s; } +/* 按钮悬停效果 */ button:hover { background-color: #f0f0f0; } +/* 按钮点击效果 */ button:active { background-color: #e0e0e0; } +/* 段落通用样式 */ p { margin-top: 10px; font-size: 14px; } +/* 文档项样式 */ .doc-item{ display: flex; justify-content: space-between; @@ -75,11 +88,14 @@ p { margin-bottom: 5px; border: 1px solid #000; } + +/* 文档项内段落样式 */ .doc-item p{ margin-top: 0; font-size: 16px; } +/* App项样式 */ .app-item { display: flex; justify-content: space-between; @@ -89,56 +105,67 @@ p { border: 1px solid #000; } +/* App信息区域样式 */ .app-info { flex: 1; } +/* App名称样式 */ .app-info h3 { font-size: 16px; margin-bottom: 5px; } +/* App日期样式 */ .app-info p { font-size: 12px; margin: 0; } +/* 下载按钮样式 */ .download-btn { padding: 8px 15px; font-size: 12px; } +/* 退出按钮样式 */ .logout-btn { margin-top: 10px; font-size: 12px; } +/* 加载指示器样式 */ #loadingIndicator { font-size: 12px; color: #666; } -/* 响应式设计 */ +/* 响应式设计 - 移动端适配 */ @media (max-width: 600px) { + /* 移动端主容器样式 */ .container { margin: 10px; padding: 15px; } + /* 移动端区块样式 */ .section { padding: 15px; } + /* 移动端App项样式,改为垂直排列 */ .app-item { flex-direction: column; align-items: flex-start; } + /* 移动端文档项样式,改为垂直排列 */ .doc-item { flex-direction: column; align-items: flex-start; } + /* 移动端下载按钮样式,添加顶部间距 */ .download-btn { margin-top: 10px; }