@axiosleo/koapp
Advanced tools
| <!DOCTYPE html> | ||
| <html lang="zh-CN"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>404 - 页面未找到 | Page Not Found</title> | ||
| <style> | ||
| * { | ||
| margin: 0; | ||
| padding: 0; | ||
| box-sizing: border-box; | ||
| } | ||
| body { | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", | ||
| "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", | ||
| Helvetica, Arial, sans-serif; | ||
| background: #f5f5f5; | ||
| min-height: 100vh; | ||
| padding: 20px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| } | ||
| .container { | ||
| max-width: 800px; | ||
| width: 100%; | ||
| background: white; | ||
| border-radius: 10px; | ||
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | ||
| overflow: hidden; | ||
| text-align: center; | ||
| } | ||
| .header { | ||
| background: linear-gradient(45deg, #ff6b6b, #ffa726); | ||
| color: white; | ||
| padding: 50px 30px; | ||
| } | ||
| .error-code { | ||
| font-size: 8rem; | ||
| font-weight: 300; | ||
| margin-bottom: 20px; | ||
| opacity: 0.9; | ||
| text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); | ||
| } | ||
| .error-title { | ||
| font-size: 2.5rem; | ||
| margin-bottom: 15px; | ||
| font-weight: 300; | ||
| } | ||
| .error-subtitle { | ||
| font-size: 1.1rem; | ||
| opacity: 0.9; | ||
| margin-bottom: 10px; | ||
| } | ||
| .error-description { | ||
| font-size: 1rem; | ||
| opacity: 0.8; | ||
| } | ||
| .content { | ||
| padding: 50px 30px; | ||
| } | ||
| .error-icon { | ||
| font-size: 6rem; | ||
| margin-bottom: 30px; | ||
| opacity: 0.6; | ||
| } | ||
| .error-message { | ||
| font-size: 1.2rem; | ||
| color: #666; | ||
| margin-bottom: 30px; | ||
| line-height: 1.6; | ||
| } | ||
| .suggestions { | ||
| text-align: left; | ||
| background: #f8f9fa; | ||
| border-radius: 8px; | ||
| padding: 30px; | ||
| margin: 30px 0; | ||
| } | ||
| .suggestions h3 { | ||
| font-size: 1.3rem; | ||
| color: #333; | ||
| margin-bottom: 20px; | ||
| display: flex; | ||
| align-items: center; | ||
| } | ||
| .suggestions h3::before { | ||
| content: "💡"; | ||
| margin-right: 10px; | ||
| font-size: 1.5rem; | ||
| } | ||
| .suggestions ul { | ||
| list-style: none; | ||
| padding: 0; | ||
| } | ||
| .suggestions li { | ||
| padding: 10px 0; | ||
| color: #666; | ||
| border-bottom: 1px solid #e0e0e0; | ||
| display: flex; | ||
| align-items: center; | ||
| } | ||
| .suggestions li:last-child { | ||
| border-bottom: none; | ||
| } | ||
| .suggestions li::before { | ||
| content: "→"; | ||
| color: #2196f3; | ||
| font-weight: bold; | ||
| margin-right: 10px; | ||
| } | ||
| .action-buttons { | ||
| display: flex; | ||
| gap: 15px; | ||
| justify-content: center; | ||
| flex-wrap: wrap; | ||
| margin-top: 30px; | ||
| } | ||
| .btn { | ||
| padding: 15px 30px; | ||
| border: none; | ||
| border-radius: 25px; | ||
| font-size: 16px; | ||
| font-weight: 500; | ||
| text-decoration: none; | ||
| transition: all 0.3s; | ||
| cursor: pointer; | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| } | ||
| .btn-primary { | ||
| background: linear-gradient(45deg, #2196f3, #21cbf3); | ||
| color: white; | ||
| } | ||
| .btn-primary:hover { | ||
| transform: translateY(-2px); | ||
| box-shadow: 0 8px 20px rgba(33, 150, 243, 0.3); | ||
| } | ||
| .btn-secondary { | ||
| background: #f5f5f5; | ||
| color: #666; | ||
| border: 2px solid #e0e0e0; | ||
| } | ||
| .btn-secondary:hover { | ||
| background: #e0e0e0; | ||
| border-color: #ccc; | ||
| transform: translateY(-2px); | ||
| } | ||
| .stats { | ||
| display: flex; | ||
| justify-content: space-around; | ||
| padding: 20px; | ||
| background: #f8f9fa; | ||
| border-top: 1px solid #e0e0e0; | ||
| } | ||
| .stat-item { | ||
| text-align: center; | ||
| } | ||
| .stat-number { | ||
| font-size: 2rem; | ||
| font-weight: bold; | ||
| color: #ff6b6b; | ||
| display: block; | ||
| } | ||
| .stat-label { | ||
| font-size: 14px; | ||
| color: #666; | ||
| margin-top: 5px; | ||
| } | ||
| .breadcrumb { | ||
| padding: 15px 30px; | ||
| background: #f8f9fa; | ||
| border-bottom: 1px solid #e0e0e0; | ||
| font-size: 14px; | ||
| color: #666; | ||
| text-align: left; | ||
| } | ||
| .breadcrumb a { | ||
| color: #2196f3; | ||
| text-decoration: none; | ||
| } | ||
| .breadcrumb a:hover { | ||
| text-decoration: underline; | ||
| } | ||
| @media (max-width: 768px) { | ||
| .container { | ||
| margin: 10px; | ||
| border-radius: 5px; | ||
| } | ||
| .header { | ||
| padding: 30px 20px; | ||
| } | ||
| .error-code { | ||
| font-size: 5rem; | ||
| } | ||
| .error-title { | ||
| font-size: 2rem; | ||
| } | ||
| .content { | ||
| padding: 30px 20px; | ||
| } | ||
| .error-icon { | ||
| font-size: 4rem; | ||
| } | ||
| .action-buttons { | ||
| flex-direction: column; | ||
| align-items: center; | ||
| } | ||
| .btn { | ||
| width: 100%; | ||
| max-width: 300px; | ||
| } | ||
| .stats { | ||
| flex-direction: column; | ||
| gap: 15px; | ||
| } | ||
| } | ||
| /* 动画效果 */ | ||
| @keyframes float { | ||
| 0%, | ||
| 100% { | ||
| transform: translateY(0px); | ||
| } | ||
| 50% { | ||
| transform: translateY(-10px); | ||
| } | ||
| } | ||
| .error-icon { | ||
| animation: float 3s ease-in-out infinite; | ||
| } | ||
| @keyframes fadeInUp { | ||
| from { | ||
| opacity: 0; | ||
| transform: translateY(30px); | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } | ||
| } | ||
| .fade-in-up { | ||
| animation: fadeInUp 0.6s ease-out; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <!-- <div class="breadcrumb"> | ||
| <a href="/" onclick="goHome()">🏠 首页</a> / | ||
| <a href="javascript:history.back()">📄 上一页</a> / | ||
| <span>404 错误</span> | ||
| </div> --> | ||
| <!-- <div class="header"> | ||
| <div class="error-code fade-in-up">404</div> | ||
| <h1 class="error-title fade-in-up">页面未找到</h1> | ||
| <p class="error-subtitle fade-in-up">Page Not Found</p> | ||
| <p class="error-description fade-in-up"> | ||
| 抱歉,您访问的页面不存在或已被移动 | ||
| </p> | ||
| </div> --> | ||
| <div class="content"> | ||
| <div class="error-icon">🔍</div> | ||
| <div class="error-message fade-in-up"> | ||
| 很抱歉,无法找到您要访问的页面。<br /> | ||
| 这可能是因为页面已被删除、移动或者您输入的网址有误。 | ||
| </div> | ||
| <div class="suggestions fade-in-up"> | ||
| <h3>您可以尝试以下操作</h3> | ||
| <ul> | ||
| <li>检查网址是否输入正确</li> | ||
| <li>返回上一页继续浏览</li> | ||
| <li>访问网站首页</li> | ||
| <li>使用搜索功能查找内容</li> | ||
| <li>联系网站管理员报告此问题</li> | ||
| </ul> | ||
| </div> | ||
| <div class="action-buttons fade-in-up"> | ||
| <a href="/" class="btn btn-primary" onclick="goHome()"> | ||
| 🏠 返回首页 | ||
| </a> | ||
| <a href="javascript:history.back()" class="btn btn-secondary"> | ||
| ← 返回上页 | ||
| </a> | ||
| <!-- <a href="javascript:location.reload()" class="btn btn-secondary"> | ||
| 🔄 刷新页面 | ||
| </a> --> | ||
| </div> | ||
| </div> | ||
| <!-- <div class="stats"> | ||
| <div class="stat-item"> | ||
| <span class="stat-number">404</span> | ||
| <div class="stat-label">错误代码</div> | ||
| </div> | ||
| <div class="stat-item"> | ||
| <span class="stat-number" id="currentTime">--:--</span> | ||
| <div class="stat-label">当前时间</div> | ||
| </div> | ||
| <div class="stat-item"> | ||
| <span class="stat-number" id="stayTime">0</span> | ||
| <div class="stat-label">停留时间(秒)</div> | ||
| </div> | ||
| </div> --> | ||
| </div> | ||
| <script> | ||
| // 返回首页功能 | ||
| function goHome() { | ||
| // 尝试多种方式返回首页 | ||
| if (window.location.origin) { | ||
| window.location.href = window.location.origin; | ||
| } else { | ||
| window.location.href = "/"; | ||
| } | ||
| } | ||
| // 更新当前时间 | ||
| function updateTime() { | ||
| const now = new Date(); | ||
| const timeString = now.toLocaleTimeString("zh-CN", { | ||
| hour12: false, | ||
| hour: "2-digit", | ||
| minute: "2-digit", | ||
| }); | ||
| document.getElementById("currentTime").textContent = timeString; | ||
| } | ||
| // 计算停留时间 | ||
| let staySeconds = 0; | ||
| function updateStayTime() { | ||
| document.getElementById("stayTime").textContent = staySeconds; | ||
| staySeconds++; | ||
| } | ||
| // 初始化 | ||
| updateTime(); | ||
| setInterval(updateTime, 1000); | ||
| setInterval(updateStayTime, 1000); | ||
| // 添加加载动画 | ||
| window.addEventListener("load", function () { | ||
| const fadeElements = document.querySelectorAll(".fade-in-up"); | ||
| fadeElements.forEach((element, index) => { | ||
| element.style.opacity = "0"; | ||
| element.style.transform = "translateY(30px)"; | ||
| setTimeout(() => { | ||
| element.style.transition = "all 0.6s ease-out"; | ||
| element.style.opacity = "1"; | ||
| element.style.transform = "translateY(0)"; | ||
| }, index * 100); | ||
| }); | ||
| }); | ||
| // 键盘快捷键 | ||
| document.addEventListener("keydown", function (e) { | ||
| if (e.key === "Escape") { | ||
| history.back(); | ||
| } else if (e.key === "Home" || (e.ctrlKey && e.key === "h")) { | ||
| e.preventDefault(); | ||
| goHome(); | ||
| } else if (e.key === "F5" || (e.ctrlKey && e.key === "r")) { | ||
| // 允许默认的刷新行为 | ||
| } | ||
| }); | ||
| // 记录访问信息(可选) | ||
| console.log("404 页面访问信息:", { | ||
| url: window.location.href, | ||
| referrer: document.referrer, | ||
| userAgent: navigator.userAgent, | ||
| timestamp: new Date().toISOString(), | ||
| }); | ||
| </script> | ||
| </body> | ||
| </html> |
@@ -22,3 +22,3 @@ { | ||
| "dependencies": { | ||
| "@axiosleo/koapp": "^1.1.0-alpha.2", | ||
| "@axiosleo/koapp": "^1.1.0", | ||
| "@axiosleo/orm-mysql": "^0.11.2", | ||
@@ -25,0 +25,0 @@ "cluster": "^0.7.7", |
+1
-1
@@ -12,3 +12,3 @@ #!/usr/bin/env node | ||
| bin: 'koapp', | ||
| version: '1.1.0-alpha.2', | ||
| version: '1.1.0', | ||
| commands_dir: path.join(__dirname, '../commands'), | ||
@@ -15,0 +15,0 @@ }); |
+469
-7
@@ -5,6 +5,7 @@ 'use strict'; | ||
| const { Command, printer } = require('@axiosleo/cli-tool'); | ||
| const { error, Router, KoaApplication, result } = require('../'); | ||
| const { Router, KoaApplication, result } = require('../'); | ||
| const path = require('path'); | ||
| const promisify = require('util').promisify; | ||
| const readdir = promisify(fs.readdir); | ||
| const stat = promisify(fs.stat); | ||
| const { | ||
@@ -16,3 +17,5 @@ _exists, | ||
| } = require('@axiosleo/cli-tool/src/helper/fs'); | ||
| const { _fixed } = require('@axiosleo/cli-tool/src/helper/str'); | ||
| const { | ||
| _fixed | ||
| } = require('@axiosleo/cli-tool/src/helper/str'); | ||
@@ -48,4 +51,34 @@ const mimeTypes = { | ||
| this.addOption('port', 'p', 'Port', 'optional', 80); | ||
| this.html404Content = null; | ||
| } | ||
| formatFileSize(bytes) { | ||
| if (bytes === 0) { return '--'; } | ||
| const k = 1024; | ||
| const sizes = ['B', 'KB', 'MB', 'GB']; | ||
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | ||
| return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; | ||
| } | ||
| getFileType(filename, is_dir, ext) { | ||
| if (is_dir) { return 'folder'; } | ||
| const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']; | ||
| const codeExts = ['js', 'ts', 'html', 'css', 'json', 'xml', 'sh', 'py', 'java', 'cpp', 'c', 'php']; | ||
| if (imageExts.includes(ext.toLowerCase())) { return 'image'; } | ||
| if (codeExts.includes(ext.toLowerCase())) { return 'code'; } | ||
| return 'document'; | ||
| } | ||
| getFileIcon(type) { | ||
| const icons = { | ||
| folder: '📁', | ||
| document: '📄', | ||
| image: '🖼️', | ||
| code: '💻' | ||
| }; | ||
| return icons[type] || '📄'; | ||
| } | ||
| async list(dir) { | ||
@@ -63,4 +96,17 @@ if (!await _exists(dir)) { | ||
| let is_dir = await _is_dir(filepath); | ||
| files.push({ filename, is_dir, filepath }); | ||
| let stats = await stat(filepath); | ||
| let size = is_dir ? 0 : stats.size; | ||
| let mtime = stats.mtime; | ||
| let ext = is_dir ? '' : _ext(filename); | ||
| files.push({ | ||
| filename, | ||
| is_dir, | ||
| filepath, | ||
| size, | ||
| mtime, | ||
| ext | ||
| }); | ||
| })); | ||
| // debug.log(files); | ||
| return files; | ||
@@ -82,3 +128,39 @@ } | ||
| if (!await _exists(d)) { | ||
| error(404, 'Not Found'); | ||
| if (!this.html404Content) { | ||
| // 读取 404.html 文件内容 | ||
| const html404Path = path.join(__dirname, '../assets/html/404.html'); | ||
| let html404Content = 'Not Found'; | ||
| try { | ||
| html404Content = fs.readFileSync(html404Path, 'utf8'); | ||
| } catch (_error) { // eslint-disable-line no-unused-vars | ||
| // 如果读取失败,使用简单的 HTML 404 页面 | ||
| html404Content = `<!DOCTYPE html> | ||
| <html lang="zh-CN"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>404 - 页面未找到</title> | ||
| <style> | ||
| body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; } | ||
| .container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } | ||
| h1 { color: #ff6b6b; font-size: 4rem; margin: 0; } | ||
| h2 { color: #333; margin: 20px 0; } | ||
| p { color: #666; line-height: 1.6; } | ||
| .btn { display: inline-block; background: #2196f3; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin-top: 20px; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <h1>404</h1> | ||
| <h2>页面未找到</h2> | ||
| <p>抱歉,您访问的页面不存在或已被移动。</p> | ||
| <a href="/" class="btn">返回首页</a> | ||
| </div> | ||
| </body> | ||
| </html>`; | ||
| } | ||
| this.html404Content = html404Content; | ||
| } | ||
| result(this.html404Content, 404, { 'Content-Type': 'text/html' }); | ||
| } | ||
@@ -99,13 +181,393 @@ if (await _is_file(d)) { | ||
| } | ||
| let htmlContent = '<ul><li><a href="../">../</a></li>'; | ||
| let files = await this.list(d); | ||
| files = files.sort((a, b) => { | ||
| // Directories first, then files, both alphabetically | ||
| if (a.is_dir && !b.is_dir) { return -1; } | ||
| if (!a.is_dir && b.is_dir) { return 1; } | ||
| return a.filename.toLowerCase() < b.filename.toLowerCase() ? -1 : 1; | ||
| }); | ||
| // Calculate statistics | ||
| const folderCount = files.filter(f => f.is_dir).length; | ||
| const fileCount = files.filter(f => !f.is_dir).length; | ||
| const totalSize = files.reduce((sum, f) => sum + (f.size || 0), 0); | ||
| // Generate breadcrumb | ||
| const pathParts = url.pathname.split('/').filter(p => p); | ||
| let breadcrumb = '<a href="/">🏠 Home</a>'; | ||
| let currentPath = ''; | ||
| pathParts.forEach(part => { | ||
| currentPath += '/' + part; | ||
| breadcrumb += ` / <a href="${currentPath}">${decodeURIComponent(part)}</a>`; | ||
| }); | ||
| // Generate file items HTML | ||
| let fileItemsHTML = ''; | ||
| // Add parent directory link if not at root | ||
| if (url.pathname !== '/') { | ||
| fileItemsHTML += ` | ||
| <div class="file-item" data-type="folder" onclick="window.location.href='../'"> | ||
| <div class="file-icon folder">📁</div> | ||
| <div class="file-info"> | ||
| <div class="file-name">../</div> | ||
| <div class="file-meta">返回上级目录</div> | ||
| </div> | ||
| <div class="file-size">--</div> | ||
| </div>`; | ||
| } | ||
| files.forEach(f => { | ||
| let p = url.pathname === '/' ? '.' : url.pathname; | ||
| p = p + '/' + f.filename; | ||
| htmlContent += `<li><a href="${p}">${f.filename}</a></li>`; | ||
| const fileType = this.getFileType(f.filename, f.is_dir, f.ext); | ||
| const fileIcon = this.getFileIcon(fileType); | ||
| const fileSize = this.formatFileSize(f.size); | ||
| const modifiedDate = f.mtime.toLocaleString('zh-CN', { | ||
| year: 'numeric', | ||
| month: '2-digit', | ||
| day: '2-digit', | ||
| hour: '2-digit', | ||
| minute: '2-digit' | ||
| }); | ||
| const fileTypeText = f.is_dir ? '文件夹' : (f.ext ? f.ext.toUpperCase() + '文件' : '文件'); | ||
| fileItemsHTML += ` | ||
| <div class="file-item" data-type="${fileType}" onclick="window.location.href='${p}'"> | ||
| <div class="file-icon ${fileType}">${fileIcon}</div> | ||
| <div class="file-info"> | ||
| <div class="file-name">${f.filename}</div> | ||
| <div class="file-meta">${fileTypeText} • 最后修改: ${modifiedDate}</div> | ||
| </div> | ||
| <div class="file-size">${fileSize}</div> | ||
| </div>`; | ||
| }); | ||
| htmlContent += '</ul>'; | ||
| const htmlContent = `<!DOCTYPE html> | ||
| <html lang="zh-CN"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>文件列表 - File List</title> | ||
| <style> | ||
| * { | ||
| margin: 0; | ||
| padding: 0; | ||
| box-sizing: border-box; | ||
| } | ||
| body { | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", | ||
| "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", | ||
| Helvetica, Arial, sans-serif; | ||
| background: #f5f5f5; | ||
| min-height: 100vh; | ||
| padding: 20px; | ||
| } | ||
| .container { | ||
| max-width: 1200px; | ||
| margin: 0 auto; | ||
| background: white; | ||
| border-radius: 10px; | ||
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | ||
| overflow: hidden; | ||
| } | ||
| .header { | ||
| background: linear-gradient(45deg, #2196f3, #21cbf3); | ||
| color: white; | ||
| padding: 30px; | ||
| text-align: center; | ||
| } | ||
| .header h1 { | ||
| font-size: 2.5rem; | ||
| margin-bottom: 10px; | ||
| font-weight: 300; | ||
| } | ||
| .header p { | ||
| font-size: 1.1rem; | ||
| opacity: 0.9; | ||
| } | ||
| .file-list { | ||
| padding: 30px; | ||
| } | ||
| .search-box { | ||
| margin-bottom: 30px; | ||
| } | ||
| .search-box input { | ||
| width: 100%; | ||
| padding: 15px 20px; | ||
| border: 2px solid #e0e0e0; | ||
| border-radius: 25px; | ||
| font-size: 16px; | ||
| transition: border-color 0.3s; | ||
| } | ||
| .search-box input:focus { | ||
| outline: none; | ||
| border-color: #2196f3; | ||
| } | ||
| .file-item { | ||
| display: flex; | ||
| align-items: center; | ||
| padding: 15px 20px; | ||
| border: 1px solid #e0e0e0; | ||
| border-radius: 8px; | ||
| margin-bottom: 10px; | ||
| transition: all 0.3s; | ||
| cursor: pointer; | ||
| } | ||
| .file-item:hover { | ||
| background: #f5f5f5; | ||
| border-color: #2196f3; | ||
| transform: translateX(5px); | ||
| } | ||
| .file-icon { | ||
| width: 40px; | ||
| height: 40px; | ||
| margin-right: 15px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border-radius: 8px; | ||
| font-size: 18px; | ||
| color: white; | ||
| } | ||
| .file-icon.folder { | ||
| background: #ffa726; | ||
| } | ||
| .file-icon.document { | ||
| background: #42a5f5; | ||
| } | ||
| .file-icon.image { | ||
| background: #66bb6a; | ||
| } | ||
| .file-icon.code { | ||
| background: #ab47bc; | ||
| } | ||
| .file-info { | ||
| flex: 1; | ||
| } | ||
| .file-name { | ||
| font-size: 16px; | ||
| font-weight: 500; | ||
| margin-bottom: 5px; | ||
| color: #333; | ||
| } | ||
| .file-meta { | ||
| font-size: 14px; | ||
| color: #666; | ||
| } | ||
| .file-size { | ||
| font-size: 14px; | ||
| color: #999; | ||
| min-width: 80px; | ||
| text-align: right; | ||
| } | ||
| .stats { | ||
| display: flex; | ||
| justify-content: space-around; | ||
| padding: 20px; | ||
| background: #f8f9fa; | ||
| border-top: 1px solid #e0e0e0; | ||
| } | ||
| .stat-item { | ||
| text-align: center; | ||
| } | ||
| .stat-number { | ||
| font-size: 2rem; | ||
| font-weight: bold; | ||
| color: #2196f3; | ||
| display: block; | ||
| } | ||
| .stat-label { | ||
| font-size: 14px; | ||
| color: #666; | ||
| margin-top: 5px; | ||
| } | ||
| .breadcrumb { | ||
| padding: 15px 30px; | ||
| background: #f8f9fa; | ||
| border-bottom: 1px solid #e0e0e0; | ||
| font-size: 14px; | ||
| color: #666; | ||
| } | ||
| .breadcrumb a { | ||
| color: #2196f3; | ||
| text-decoration: none; | ||
| } | ||
| .breadcrumb a:hover { | ||
| text-decoration: underline; | ||
| } | ||
| @media (max-width: 768px) { | ||
| .container { | ||
| margin: 10px; | ||
| border-radius: 5px; | ||
| } | ||
| .header { | ||
| padding: 20px; | ||
| } | ||
| .header h1 { | ||
| font-size: 2rem; | ||
| } | ||
| .file-list { | ||
| padding: 20px; | ||
| } | ||
| .file-item { | ||
| padding: 10px 15px; | ||
| } | ||
| .stats { | ||
| flex-direction: column; | ||
| gap: 15px; | ||
| } | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <div class="header"> | ||
| <h1>📁 文件管理器</h1> | ||
| <p>File Manager</p> | ||
| </div> | ||
| <div class="breadcrumb"> | ||
| ${breadcrumb} | ||
| </div> | ||
| <div class="file-list"> | ||
| <div class="search-box"> | ||
| <input | ||
| type="text" | ||
| placeholder="🔍 在当前目录下搜索..." | ||
| id="searchInput" | ||
| /> | ||
| </div> | ||
| <div id="fileContainer"> | ||
| ${fileItemsHTML} | ||
| </div> | ||
| </div> | ||
| <div class="stats"> | ||
| <div class="stat-item"> | ||
| <span class="stat-number">${folderCount}</span> | ||
| <div class="stat-label">文件夹</div> | ||
| </div> | ||
| <div class="stat-item"> | ||
| <span class="stat-number">${fileCount}</span> | ||
| <div class="stat-label">文件</div> | ||
| </div> | ||
| <div class="stat-item"> | ||
| <span class="stat-number">${this.formatFileSize(totalSize)}</span> | ||
| <div class="stat-label">总大小</div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <script> | ||
| // 搜索功能 | ||
| const searchInput = document.getElementById("searchInput"); | ||
| const fileContainer = document.getElementById("fileContainer"); | ||
| const fileItems = document.querySelectorAll(".file-item"); | ||
| searchInput.addEventListener("input", function () { | ||
| const searchTerm = this.value.toLowerCase(); | ||
| fileItems.forEach((item) => { | ||
| const fileName = item | ||
| .querySelector(".file-name") | ||
| .textContent.toLowerCase(); | ||
| const fileMeta = item | ||
| .querySelector(".file-meta") | ||
| .textContent.toLowerCase(); | ||
| if (fileName.includes(searchTerm) || fileMeta.includes(searchTerm)) { | ||
| item.style.display = "flex"; | ||
| } else { | ||
| item.style.display = "none"; | ||
| } | ||
| }); | ||
| // 更新统计信息 | ||
| updateStats(); | ||
| }); | ||
| // 更新统计信息 | ||
| function updateStats() { | ||
| const visibleItems = Array.from(fileItems).filter( | ||
| (item) => item.style.display !== "none" | ||
| ); | ||
| const folders = visibleItems.filter( | ||
| (item) => item.getAttribute("data-type") === "folder" | ||
| ).length; | ||
| const files = visibleItems.length - folders; | ||
| document.querySelector( | ||
| ".stats .stat-item:nth-child(1) .stat-number" | ||
| ).textContent = folders; | ||
| document.querySelector( | ||
| ".stats .stat-item:nth-child(2) .stat-number" | ||
| ).textContent = files; | ||
| } | ||
| // 键盘快捷键 | ||
| document.addEventListener("keydown", function (e) { | ||
| if (e.ctrlKey || e.metaKey) { | ||
| if (e.key === "f") { | ||
| e.preventDefault(); | ||
| searchInput.focus(); | ||
| } | ||
| } | ||
| }); | ||
| // 添加加载动画 | ||
| window.addEventListener("load", function () { | ||
| fileItems.forEach((item, index) => { | ||
| item.style.opacity = "0"; | ||
| item.style.transform = "translateY(20px)"; | ||
| setTimeout(() => { | ||
| item.style.transition = "all 0.3s ease"; | ||
| item.style.opacity = "1"; | ||
| item.style.transform = "translateY(0)"; | ||
| }, index * 50); | ||
| }); | ||
| }); | ||
| </script> | ||
| </body> | ||
| </html>`; | ||
| // debug.log('Generated HTML file listing'); | ||
| result(htmlContent, 200, { 'Content-Type': 'text/html' }); | ||
@@ -112,0 +574,0 @@ }], |
+1
-1
| { | ||
| "name": "@axiosleo/koapp", | ||
| "version": "1.1.0-alpha.2", | ||
| "version": "1.1.0", | ||
| "description": "", | ||
@@ -5,0 +5,0 @@ "author": "AxiosLeo", |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
155082
16.98%48
2.13%4032
10.95%1
-50%5
150%