Spaces:
Running
Running
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>日志管理</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| background: white; | |
| border-radius: 15px; | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.1); | |
| overflow: hidden; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 30px; | |
| text-align: center; | |
| } | |
| .header h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 10px; | |
| font-weight: 300; | |
| } | |
| .header p { | |
| opacity: 0.9; | |
| font-size: 1.1rem; | |
| } | |
| .controls { | |
| padding: 30px; | |
| background: #f8f9fa; | |
| border-bottom: 1px solid #e9ecef; | |
| } | |
| .search-container { | |
| display: flex; | |
| gap: 15px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| .search-input { | |
| flex: 1; | |
| min-width: 250px; | |
| padding: 12px 20px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 25px; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| .search-input:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| } | |
| .btn { | |
| padding: 12px 25px; | |
| border: none; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| text-decoration: none; | |
| display: inline-block; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); | |
| } | |
| .btn-secondary { | |
| background: #6c757d; | |
| color: white; | |
| } | |
| .btn-secondary:hover { | |
| background: #5a6268; | |
| transform: translateY(-2px); | |
| } | |
| .page-size-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-top: 20px; | |
| } | |
| .page-size-container label { | |
| font-weight: 500; | |
| color: #495057; | |
| } | |
| .page-size-container select { | |
| padding: 8px 15px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| background: white; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .page-size-container select:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| } | |
| .content { | |
| padding: 30px; | |
| } | |
| .log-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-bottom: 30px; | |
| background: white; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| .log-table th { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 15px; | |
| text-align: left; | |
| font-weight: 500; | |
| } | |
| .log-table td { | |
| padding: 15px; | |
| border-bottom: 1px solid #e9ecef; | |
| vertical-align: middle; | |
| } | |
| .log-table tr:hover { | |
| background: #f8f9fa; | |
| } | |
| .log-table tr:last-child td { | |
| border-bottom: none; | |
| } | |
| .username { | |
| font-weight: 600; | |
| color: #495057; | |
| } | |
| .token { | |
| font-family: 'Courier New', monospace; | |
| background: #f8f9fa; | |
| padding: 8px 12px; | |
| border-radius: 4px; | |
| font-size: 0.85rem; | |
| color: #6c757d; | |
| word-break: break-all; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| border: 1px solid #e9ecef; | |
| } | |
| .token:hover { | |
| background: #e9ecef; | |
| border-color: #667eea; | |
| } | |
| .token::after { | |
| content: "点击复制"; | |
| position: absolute; | |
| top: -30px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: #333; | |
| color: white; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.3s ease; | |
| white-space: nowrap; | |
| } | |
| .token:hover::after { | |
| opacity: 1; | |
| } | |
| .copy-success { | |
| background: #d4edda ; | |
| border-color: #28a745 ; | |
| color: #155724 ; | |
| } | |
| .content { | |
| word-wrap: break-word; | |
| line-height: 1.4; | |
| } | |
| .timestamp { | |
| color: #6c757d; | |
| font-size: 0.9rem; | |
| } | |
| .pagination { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 20px; | |
| margin-top: 30px; | |
| flex-wrap: wrap; | |
| } | |
| .pagination .page-size-container { | |
| margin: 0; | |
| } | |
| .pagination-controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .pagination button { | |
| padding: 8px 12px; | |
| border: 1px solid #dee2e6; | |
| background: white; | |
| cursor: pointer; | |
| border-radius: 5px; | |
| transition: all 0.3s ease; | |
| } | |
| .pagination button:hover:not(:disabled) { | |
| background: #667eea; | |
| color: white; | |
| border-color: #667eea; | |
| } | |
| .pagination button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| .pagination .active { | |
| background: #667eea; | |
| color: white; | |
| border-color: #667eea; | |
| } | |
| .page-info { | |
| color: #6c757d; | |
| margin: 0 15px; | |
| } | |
| .no-data { | |
| text-align: center; | |
| padding: 60px 20px; | |
| color: #6c757d; | |
| } | |
| .no-data i { | |
| font-size: 4rem; | |
| margin-bottom: 20px; | |
| opacity: 0.5; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| margin: 10px; | |
| border-radius: 10px; | |
| } | |
| .header { | |
| padding: 20px; | |
| } | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| .controls { | |
| padding: 20px; | |
| } | |
| .search-container { | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| .search-input { | |
| min-width: auto; | |
| } | |
| .stats { | |
| justify-content: center; | |
| } | |
| .content { | |
| padding: 20px; | |
| } | |
| .log-table { | |
| font-size: 0.9rem; | |
| } | |
| .log-table th, | |
| .log-table td { | |
| padding: 10px 8px; | |
| } | |
| .error-content { | |
| max-width: 200px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>日志管理系统</h1> | |
| </div> | |
| <div class="controls"> | |
| <div class="search-container"> | |
| <input type="text" id="searchInput" class="search-input" placeholder="搜索用户名..."> | |
| <button class="btn btn-primary" onclick="searchLogs()">搜索</button> | |
| <button class="btn btn-secondary" onclick="refreshLogs()">刷新</button> | |
| </div> | |
| </div> | |
| <div class="content"> | |
| <table class="log-table" id="logTable"> | |
| <thead> | |
| <tr> | |
| <th>Type</th> | |
| <th>Source</th> | |
| <th>UID</th> | |
| <th>Username</th> | |
| <th>Token</th> | |
| <th>Content</th> | |
| <th>Timestamp</th> | |
| </tr> | |
| </thead> | |
| <tbody id="logTableBody"> | |
| </tbody> | |
| </table> | |
| <div class="no-data" id="noData" style="display: none;"> | |
| <div>📋</div> | |
| <h3>暂无数据</h3> | |
| <p>没有找到匹配的错误日志</p> | |
| </div> | |
| <div class="pagination" id="pagination"> | |
| <div class="page-size-container"> | |
| <label for="pageSize">每页显示:</label> | |
| <select id="pageSize" onchange="changePageSize()"> | |
| <option value="20" selected>20条</option> | |
| <option value="40">40条</option> | |
| <option value="60">60条</option> | |
| </select> | |
| </div> | |
| <div class="pagination-controls"> | |
| <button onclick="goToPage(1)" id="firstBtn">首页</button> | |
| <button onclick="goToPage(currentPage - 1)" id="prevBtn">上一页</button> | |
| <span class="page-info" id="pageInfo">第 1 页,共 1 页</span> | |
| <button onclick="goToPage(currentPage + 1)" id="nextBtn">下一页</button> | |
| <button onclick="goToPage(totalPages)" id="lastBtn">末页</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const data = {{ data | tojson}}; | |
| // 分页配置 | |
| let currentPage = 1; | |
| let pageSize = 20; | |
| let totalPages = 1; | |
| let filteredLogs = [...data]; | |
| let searchTerm = ''; | |
| // 初始化页面 | |
| function initPage() { | |
| renderLogs(); | |
| updatePagination(); | |
| } | |
| // 改变每页显示数量 | |
| function changePageSize() { | |
| const select = document.getElementById('pageSize'); | |
| pageSize = parseInt(select.value); | |
| currentPage = 1; // 重置到第一页 | |
| renderLogs(); | |
| updatePagination(); | |
| } | |
| // 渲染日志表格 | |
| function renderLogs() { | |
| const tbody = document.getElementById('logTableBody'); | |
| const noData = document.getElementById('noData'); | |
| const table = document.getElementById('logTable'); | |
| if (filteredLogs.length === 0) { | |
| table.style.display = 'none'; | |
| noData.style.display = 'block'; | |
| return; | |
| } | |
| table.style.display = 'table'; | |
| noData.style.display = 'none'; | |
| totalPages = Math.ceil(filteredLogs.length / pageSize); | |
| const startIndex = (currentPage - 1) * pageSize; | |
| const endIndex = startIndex + pageSize; | |
| const currentLogs = filteredLogs.slice(startIndex, endIndex); | |
| tbody.innerHTML = ''; | |
| currentLogs.forEach(log => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td> | |
| <div class="type">${log.type}</div> | |
| </td> | |
| <td> | |
| <div class="uid">${log.source}</div> | |
| </td> | |
| <td> | |
| <div class="uid">${log.uid}</div> | |
| </td> | |
| <td> | |
| <div class="username">${log.username}</div> | |
| </td> | |
| <td> | |
| <div class="token" onclick="copyToken('${log.token}')" title="点击复制">${log.token}</div> | |
| </td> | |
| <td> | |
| <div class="content">${log.content}</div> | |
| </td> | |
| <td> | |
| <div class="timestamp">${log.timestamp}</div> | |
| </td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| } | |
| // 复制token到剪贴板 | |
| function copyToken(token) { | |
| navigator.clipboard.writeText(token).then(function() { | |
| // 显示复制成功的视觉反馈 | |
| const tokenElements = document.querySelectorAll('.token'); | |
| tokenElements.forEach(el => { | |
| if (el.textContent === token) { | |
| el.classList.add('copy-success'); | |
| setTimeout(() => { | |
| el.classList.remove('copy-success'); | |
| }, 1000); | |
| } | |
| }); | |
| }).catch(function(err) { | |
| console.error('复制失败: ', err); | |
| // 降级方案:使用旧的复制方法 | |
| const textArea = document.createElement('textarea'); | |
| textArea.value = token; | |
| document.body.appendChild(textArea); | |
| textArea.select(); | |
| document.execCommand('copy'); | |
| document.body.removeChild(textArea); | |
| }); | |
| } | |
| // 更新分页控件 | |
| function updatePagination() { | |
| const pageInfo = document.getElementById('pageInfo'); | |
| const firstBtn = document.getElementById('firstBtn'); | |
| const prevBtn = document.getElementById('prevBtn'); | |
| const nextBtn = document.getElementById('nextBtn'); | |
| const lastBtn = document.getElementById('lastBtn'); | |
| pageInfo.textContent = `第 ${currentPage} 页,共 ${totalPages} 页`; | |
| firstBtn.disabled = currentPage === 1; | |
| prevBtn.disabled = currentPage === 1; | |
| nextBtn.disabled = currentPage === totalPages; | |
| lastBtn.disabled = currentPage === totalPages; | |
| } | |
| // 跳转到指定页面 | |
| function goToPage(page) { | |
| if (page < 1 || page > totalPages) return; | |
| currentPage = page; | |
| renderLogs(); | |
| updatePagination(); | |
| } | |
| // 搜索日志 | |
| function searchLogs() { | |
| searchTerm = document.getElementById('searchInput').value.trim(); | |
| if (searchTerm === '') { | |
| filteredLogs = [...data]; | |
| } else { | |
| filteredLogs = data.filter(log => | |
| log.username.toLowerCase().includes(searchTerm.toLowerCase()) | |
| ); | |
| } | |
| currentPage = 1; | |
| renderLogs(); | |
| updatePagination(); | |
| } | |
| // 刷新日志 | |
| function refreshLogs() { | |
| // 模拟刷新数据 | |
| console.log('刷新日志数据...'); | |
| initPage(); | |
| } | |
| // 监听回车键搜索 | |
| document.getElementById('searchInput').addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| searchLogs(); | |
| } | |
| }); | |
| // 页面加载完成后初始化 | |
| document.addEventListener('DOMContentLoaded', initPage); | |
| </script> | |
| </body> | |
| </html> |