From fc6ce0e7ff5d1dc80b41d69b7bb931e8a7046f07 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Thu, 23 Jul 2026 08:27:27 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84static=E7=9A=84=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/public/__view.html | 447 +------------------------------------ static/public/css/view.css | 360 +++++++++++++++++++++++++++++ static/public/js/view.js | 179 +++++++++++++++ static/requirements.txt | 8 - 4 files changed, 544 insertions(+), 450 deletions(-) create mode 100644 static/public/css/view.css create mode 100644 static/public/js/view.js diff --git a/static/public/__view.html b/static/public/__view.html index e4bed51..b0c05ec 100644 --- a/static/public/__view.html +++ b/static/public/__view.html @@ -4,271 +4,14 @@ 文件浏览 - +

📁 文件浏览

@@ -277,7 +20,7 @@
@@ -300,186 +43,6 @@
- + diff --git a/static/public/css/view.css b/static/public/css/view.css new file mode 100644 index 0000000..4824821 --- /dev/null +++ b/static/public/css/view.css @@ -0,0 +1,360 @@ +:root { + --bg: #fdf6ee; + --fg: #4a3728; + --fg-secondary: #8c7260; + --card: #ffffff; + --card-hover: #fff7ef; + --card-active: #ffeedd; + --border: #f0e0cc; + --border-light: #f7ecdd; + --muted: #b89b7a; + --accent: #e8853a; + --accent-dark: #cf6b1f; + --accent-soft: rgba(232, 133, 58, 0.10); + --accent-glow: rgba(232, 133, 58, 0.25); + --play: #f0a030; + --download: #e8853a; + --error: #d94f3d; + --error-bg: #fef0ee; + --icon-bg: #faf0e4; + --icon-border: #f0e0cc; + --shadow-sm: 0 1px 3px rgba(74, 55, 40, 0.06); + --shadow-md: 0 4px 12px rgba(74, 55, 40, 0.08); + --shadow-lg: 0 8px 24px rgba(74, 55, 40, 0.12); + --radius-sm: 10px; + --radius-md: 14px; + --radius-lg: 20px; +} +@media (prefers-color-scheme: dark) { + :root { + --bg: #1e1712; + --fg: #f0e4d8; + --fg-secondary: #a08c78; + --card: #2a2119; + --card-hover: #342a20; + --card-active: #3e3228; + --border: #3e3228; + --border-light: #342a20; + --muted: #8c7a66; + --accent: #e8934a; + --accent-dark: #f0a060; + --accent-soft: rgba(232, 147, 74, 0.12); + --accent-glow: rgba(232, 147, 74, 0.20); + --play: #f0a848; + --download: #e8934a; + --error: #e8685a; + --error-bg: #3a2220; + --icon-bg: #342a20; + --icon-border: #4a3c30; + --shadow-sm: 0 1px 3px rgba(0,0,0,0.2); + --shadow-md: 0 4px 12px rgba(0,0,0,0.3); + --shadow-lg: 0 8px 24px rgba(0,0,0,0.4); + } +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', Roboto, Arial, sans-serif; + background: var(--bg); + color: var(--fg); + min-height: 100vh; + -webkit-font-smoothing: antialiased; +} + +/* ===== 顶栏 ===== */ +.header { + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%); + color: #fff; + padding: 0 20px; + height: 54px; + position: sticky; + top: 0; + z-index: 100; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2px 12px rgba(207, 107, 31, 0.3); +} +.header h1 { + font-size: 17px; + font-weight: 600; + letter-spacing: 0.3px; + display: flex; + align-items: center; + gap: 8px; +} +.header-actions { display: flex; gap: 4px; } +.header-actions a { + color: #fff; + text-decoration: none; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + font-size: 16px; + transition: background 0.15s; +} +.header-actions a:hover { background: rgba(255,255,255,0.18); } +.header-actions a:active { background: rgba(255,255,255,0.30); } + +/* ===== 面包屑 ===== */ +.breadcrumb { + padding: 10px 20px; + background: var(--card); + font-size: 13px; + overflow-x: auto; + white-space: nowrap; + border-bottom: 1px solid var(--border-light); + scrollbar-width: none; +} +.breadcrumb::-webkit-scrollbar { display: none; } +.breadcrumb a { + color: var(--accent); + text-decoration: none; + padding: 4px 10px; + border-radius: 8px; + transition: background 0.15s, color 0.15s; + font-weight: 500; +} +.breadcrumb a:hover { background: var(--accent-soft); } +.breadcrumb .sep { color: var(--muted); margin: 0 2px; font-size: 11px; } +.breadcrumb .current { + color: var(--fg); + font-weight: 600; + padding: 4px 10px; +} + +/* ===== 搜索栏 ===== */ +.search-box { + padding: 12px 20px; + background: var(--card); + border-bottom: 1px solid var(--border-light); +} +.search-box input { + width: 100%; + max-width: 560px; + padding: 10px 18px; + border: 1.5px solid var(--border); + border-radius: 12px; + font-size: 14px; + outline: none; + background: var(--bg); + color: var(--fg); + transition: border-color 0.2s, box-shadow 0.2s; +} +.search-box input::placeholder { color: var(--muted); } +.search-box input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +/* ===== 统计条 ===== */ +.stats { + padding: 10px 20px; + font-size: 12px; + color: var(--fg-secondary); + display: flex; + gap: 8px; + flex-wrap: wrap; + align-items: center; +} +.stats span { + padding: 4px 12px; + background: var(--card); + border-radius: 20px; + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + font-variant-numeric: tabular-nums; +} + +/* ===== 主容器 ===== */ +.container { + padding: 16px 20px 40px; + max-width: 1200px; + margin: 0 auto; +} + +/* ===== 文件网格 ===== */ +.file-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(155px, 1fr)); + gap: 14px; +} + +.item { + background: var(--card); + border-radius: var(--radius-lg); + padding: 18px 14px 14px; + cursor: pointer; + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + transition: box-shadow 0.2s, transform 0.15s, border-color 0.2s, background 0.15s; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 10px; + position: relative; +} +.item:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); + border-color: var(--border); + background: var(--card-hover); +} +.item:active { + transform: translateY(0) scale(0.98); + background: var(--card-active); +} + +/* 方块图标 */ +.item .icon { + font-size: 34px; + width: 62px; + height: 62px; + display: flex; + align-items: center; + justify-content: center; + background: var(--icon-bg); + border-radius: var(--radius-md); + border: 1px solid var(--icon-border); + transition: transform 0.15s; + flex-shrink: 0; +} +.item:hover .icon { transform: scale(1.06); } + +.item .info { + width: 100%; + display: flex; + flex-direction: column; + gap: 4px; +} +.item .name { + font-size: 13px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + line-height: 1.3; +} +.item .meta { + font-size: 11px; + color: var(--muted); + display: flex; + justify-content: center; + gap: 6px; + flex-wrap: wrap; + line-height: 1.4; +} + +/* 操作按钮 */ +.item .actions { + display: flex; + gap: 6px; + justify-content: center; + margin-top: 2px; +} +.item .actions button { + padding: 5px 14px; + font-size: 12px; + border: none; + border-radius: 10px; + cursor: pointer; + color: #fff; + font-weight: 500; + transition: opacity 0.15s, transform 0.1s; + line-height: 1.4; +} +.item .actions button:hover { opacity: 0.85; } +.item .actions button:active { transform: scale(0.93); } +.btn-play { background: var(--play); } +.btn-download { background: var(--download); } + +/* ===== 加载态 ===== */ +.loading { + text-align: center; + padding: 60px 20px; + color: var(--muted); + font-size: 14px; +} +.loading .spinner { + display: inline-flex; + align-items: center; + justify-content: center; + animation: spin 1.2s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; + font-size: 28px; + width: 56px; + height: 56px; + background: var(--card); + border-radius: var(--radius-md); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + margin-bottom: 12px; +} +@keyframes spin { 100% { transform: rotate(360deg); } } + +/* ===== 空态 ===== */ +.empty { + text-align: center; + padding: 80px 20px; + color: var(--muted); + font-size: 14px; +} +.empty .icon { + font-size: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 80px; + height: 80px; + background: var(--card); + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + margin-bottom: 16px; +} + +/* ===== 错误态 ===== */ +.error { + text-align: center; + padding: 40px 24px; + color: var(--error); + background: var(--error-bg); + border-radius: var(--radius-lg); + border: 1px solid var(--error); + margin: 20px 0; + font-size: 14px; + line-height: 1.6; +} + +/* ===== 响应式 ===== */ +@media (max-width: 480px) { + .header { padding: 0 14px; height: 50px; } + .header h1 { font-size: 15px; } + .breadcrumb { padding: 8px 14px; } + .search-box { padding: 10px 14px; } + .stats { padding: 8px 14px; gap: 6px; } + .container { padding: 12px 14px 32px; } + .file-grid { + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 10px; + } + .item { + padding: 14px 10px 12px; + border-radius: 16px; + gap: 8px; + } + .item .icon { + font-size: 28px; + width: 52px; + height: 52px; + border-radius: var(--radius-sm); + } + .item .name { font-size: 12px; } + .item .meta { font-size: 10px; gap: 4px; } + .item .actions button { padding: 4px 10px; font-size: 11px; border-radius: 8px; } +} diff --git a/static/public/js/view.js b/static/public/js/view.js new file mode 100644 index 0000000..ec82463 --- /dev/null +++ b/static/public/js/view.js @@ -0,0 +1,179 @@ +// ===== 路径解析:从 URL 取当前目录(去除首尾斜杠) ===== +const currentPath = decodeURIComponent(window.location.pathname).replace(/^\/+|\/+$/g, ''); + +// ===== DOM 引用(id 与原版本完全一致,保持后端/前端契约不变) ===== +const fileList = document.getElementById("fileList"); +const loading = document.getElementById("loading"); +const empty = document.getElementById("empty"); +const searchInput = document.getElementById("searchInput"); +const totalItems = document.getElementById("totalItems"); +const totalDirs = document.getElementById("totalDirs"); +const totalFiles = document.getElementById("totalFiles"); +const totalVideos = document.getElementById("totalVideos"); +const totalAudios = document.getElementById("totalAudios"); +const btnRefresh = document.getElementById("btnRefresh"); +const breadcrumb = document.getElementById("breadcrumb"); + +// ===== 状态 ===== +let items = []; +let searchQuery = ""; + +// ===== 工具函数 ===== +const SIZE_UNITS = ["B", "KB", "MB", "GB"]; +function formatSize(bytes) { + if (bytes === 0) return "0B"; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, i)).toFixed(1) + SIZE_UNITS[i]; +} + +const FILE_ICONS = { + pdf: "📄", doc: "📝", docx: "📝", xls: "📊", xlsx: "📊", + zip: "📦", rar: "📦", "7z": "📦", + jpg: "🖼️", jpeg: "🖼️", png: "🖼️", gif: "🖼️", + txt: "📃", json: "📋", html: "🌐", css: "🎨", js: "⚡" +}; +function getIcon(item) { + if (item.type === "dir") return "📁"; + if (item.type === "video") return "🎬"; + if (item.type === "audio") return "🎵"; + const ext = item.name.split(".").pop().toLowerCase(); + return FILE_ICONS[ext] || "📄"; +} + +// 转义文本,防止文件名含特殊字符导致 XSS 或模板断裂 +function esc(str) { + return String(str) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +// ===== 导航行为(与原版本一致) ===== +function navigate(path, type) { + if (type === "dir") window.location.href = "/" + path; + else if (type === "video" || type === "audio") window.location.href = "/video/" + path; + else window.location.href = "/" + path; +} +function playMedia(path) { window.location.href = "/video/" + path; } +function downloadFile(path) { window.location.href = "/" + path + "?download=true"; } + +// ===== 面包屑 ===== +function renderBreadcrumb() { + const parts = currentPath ? currentPath.split('/') : []; + const frags = ['根目录']; + let acc = ''; + parts.forEach((part, i) => { + if (i === parts.length - 1) { + frags.push(`›${esc(part)}`); + } else { + acc += (i === 0 ? '' : '/') + part; + frags.push(`›${esc(part)}`); + } + }); + breadcrumb.innerHTML = frags.join(''); +} + +// ===== 列表渲染(使用转义,避免文件名注入) ===== +function render() { + const q = searchQuery.toLowerCase(); + const filtered = searchQuery + ? items.filter(i => i.name.toLowerCase().includes(q)) + : items; + + totalItems.textContent = filtered.length; + totalDirs.textContent = filtered.filter(i => i.type === "dir").length; + totalFiles.textContent = filtered.filter(i => i.type === "file").length; + totalVideos.textContent = filtered.filter(i => i.type === "video").length; + totalAudios.textContent = filtered.filter(i => i.type === "audio").length; + + if (filtered.length === 0) { + fileList.innerHTML = ""; + empty.style.display = "block"; + loading.style.display = "none"; + return; + } + empty.style.display = "none"; + loading.style.display = "none"; + + fileList.innerHTML = filtered.map(item => { + const path = esc(item.path); + const type = esc(item.type); + const name = esc(item.name); + const sizeOrDir = item.type !== "dir" + ? `${formatSize(item.size)}` + : '📁 文件夹'; + const playBtn = (item.type === "video" || item.type === "audio") + ? `` + : ''; + return ` +
+ ${getIcon(item)} +
+
${name}
+
+ ${sizeOrDir} + ${esc(item.modified)} +
+
+
+ ${playBtn} + +
+
`; + }).join(""); +} + +// ===== 数据拉取(接口与参数与原版本一致) ===== +async function loadData() { + loading.style.display = "block"; + fileList.innerHTML = ""; + empty.style.display = "none"; + + try { + const url = `/api/filelist?path=${encodeURIComponent(currentPath)}`; + const res = await fetch(url); + const data = await res.json(); + if (data.success) { + items = data.items || []; + render(); + } else { + throw new Error(data.error || "加载失败"); + } + } catch (e) { + fileList.innerHTML = `
❌ 加载失败: ${esc(e.message)}
`; + loading.style.display = "none"; + } +} + +// ===== 事件绑定(快捷键 / 搜索 / 刷新 与原版本一致) ===== +let searchTimer; +searchInput.addEventListener("input", function () { + clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + searchQuery = this.value.trim(); + render(); + }, 200); +}); + +document.addEventListener("keydown", function (e) { + if ((e.ctrlKey && e.key === "f") || + (e.key === "/" && !e.ctrlKey && !e.metaKey)) { + e.preventDefault(); + searchInput.focus(); + searchInput.select(); + } + if (e.key === "Escape") { + searchInput.value = ""; + searchQuery = ""; + render(); + searchInput.blur(); + } +}); + +btnRefresh.addEventListener("click", loadData); + +// ===== 启动 ===== +renderBreadcrumb(); +loadData(); diff --git a/static/requirements.txt b/static/requirements.txt index 19103f6..c2059c5 100644 --- a/static/requirements.txt +++ b/static/requirements.txt @@ -1,11 +1,3 @@ nuitka Flask>=2.3.0,<3.0.0 Flask-CORS>=4.0.0,<5.0.0 -Flask-SQLAlchemy>=3.1.0,<4.0.0 -Flask-Limiter>=3.3.0,<4.0.0 -Flask-SocketIO>=5.3.0,<6.0.0 -python-socketio>=5.10.0,<6.0.0 -Werkzeug>=2.3.0,<3.0.0 -requests>=2.31.0,<3.0.0 -websocket-client>=1.6.0,<2.0.0 -python-engineio>=4.7.0,<5.0.0