tanjingyu 1 неделя назад
Родитель
Сommit
83c73ee3cd
2 измененных файлов с 96 добавлено и 53 удалено
  1. 6 6
      app/main.py
  2. 90 47
      app/static/console.html

+ 6 - 6
app/main.py

@@ -83,14 +83,14 @@ def build_file_tree(files: List[DataFile]) -> list:
 
 @app.get("/")
 def read_root():
-    """Serve the frontend UI."""
-    return FileResponse(os.path.join(STATIC_DIR, "index.html"), media_type="text/html")
+    """Serve the unified console UI."""
+    return FileResponse(os.path.join(STATIC_DIR, "console.html"), media_type="text/html")
 
 
-@app.get("/console")
-def console_page():
-    """Serve the console UI."""
-    return FileResponse(os.path.join(STATIC_DIR, "console.html"), media_type="text/html")
+@app.get("/fs")
+def filesystem_page():
+    """Serve the legacy file system UI."""
+    return FileResponse(os.path.join(STATIC_DIR, "index.html"), media_type="text/html")
 
 
 @app.get("/api/health")

+ 90 - 47
app/static/console.html

@@ -363,30 +363,46 @@
             }
         }
 
-        .table-header {
-            display: grid;
-            grid-template-columns: minmax(200px, 3fr) 120px 140px 100px 80px;
-            gap: 16px;
-            padding: 12px 20px;
-            background: rgba(0, 0, 0, 0.2);
+        /* Version card */
+        .version-card {
+            background: var(--bg-card);
+            border: 1px solid var(--border-card);
+            border-radius: var(--radius);
+            overflow: hidden;
+            margin-bottom: 16px;
+            animation: fadeUp 0.35s ease forwards;
+            opacity: 0;
+        }
+
+        .version-card:hover {
+            border-color: rgba(255, 255, 255, 0.1);
+        }
+
+        .version-head {
+            display: flex;
+            align-items: center;
+            gap: 14px;
+            padding: 14px 20px;
             border-bottom: 1px solid var(--border);
+            background: var(--bg-card-head);
+            flex-wrap: wrap;
+        }
+
+        .v-author {
             font-size: 13px;
-            font-weight: 600;
             color: var(--text-secondary);
-            position: sticky;
-            top: 0;
-            z-index: 10;
         }
 
-        .table-body {
-            display: flex;
-            flex-direction: column;
+        .v-time {
+            margin-left: auto;
+            font-size: 12px;
+            color: var(--text-muted);
         }
 
         /* File row grid */
         .file-row {
             display: grid;
-            grid-template-columns: minmax(200px, 3fr) 120px 140px 100px 80px;
+            grid-template-columns: 1fr 100px;
             gap: 16px;
             align-items: center;
             padding: 12px 20px;
@@ -452,8 +468,21 @@
         }
 
         .commit-tag {
+            display: inline-flex;
+            align-items: center;
+            gap: 5px;
             font-family: 'JetBrains Mono', monospace;
+            font-size: 13px;
+            font-weight: 500;
             color: var(--accent);
+            background: var(--accent-dim);
+            padding: 3px 10px;
+            border-radius: 5px;
+        }
+
+        .commit-tag svg {
+            width: 14px;
+            height: 14px;
         }
 
         .btn-dl-wrap {
@@ -490,7 +519,7 @@
         /* File group (folder) */
         .fg-header {
             display: grid;
-            grid-template-columns: minmax(200px, 3fr) 120px 140px 100px 80px;
+            grid-template-columns: 1fr 100px;
             gap: 16px;
             align-items: center;
             padding: 12px 20px;
@@ -794,22 +823,19 @@
                 $('contentBody').innerHTML = '<div class="state-box"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg><h2>暂无数据</h2><p>该阶段还没有提交记录</p></div>';
                 return;
             }
-            let h = `<div style="background: var(--bg-card); border: 1px solid var(--border-card); border-radius: var(--radius); overflow: hidden;">
-                <div class="table-header">
-                    <div>文件 / 文件夹</div>
-                    <div>Commit ID</div>
-                    <div>提交时间</div>
-                    <div>作者</div>
-                    <div style="text-align: right;">操作</div>
-                </div>
-                <div class="table-body">`;
+            let h = '';
 
             S.versions.forEach((v, i) => {
                 const groups = groupFiles(v.files);
-                h += renderGroups(groups, v);
+                h += `<div class="version-card" style="animation-delay:${Math.min(i, 10) * 0.05}s">
+            <div class="version-head">
+                <span class="commit-tag">${IC.commit} ${esc(v.commit_id.substring(0, 8))}</span>
+                <span class="v-author">${v.author ? esc(v.author) : ''}</span>
+                <span class="v-time" title="${fmtTime(v.created_at)}">${relTime(v.created_at)}</span>
+            </div>
+            <div class="version-files">${renderGroups(groups, v)}</div>
+        </div>`;
             });
-
-            h += `</div></div>`;
             if (S.hasMore) {
                 h += '<div class="load-more"><button class="load-more-btn" onclick="loadMore()">加载更多</button></div>';
             }
@@ -825,21 +851,40 @@
         // ============ File Grouping ============
         function groupFiles(files) {
             if (!files || !files.length) return [];
-            const map = {};
+
+            const topLevelGroups = {};
+            const rootFiles = [];
+
             files.forEach(f => {
                 const parts = f.relative_path.split('/');
-                const parentDir = parts.slice(0, -1).join('/');
-                if (!map[parentDir]) map[parentDir] = [];
-                map[parentDir].push(f);
+                if (parts.length === 1) {
+                    rootFiles.push(f);
+                } else {
+                    const topDir = parts[0];
+                    if (!topLevelGroups[topDir]) topLevelGroups[topDir] = [];
+                    topLevelGroups[topDir].push(f);
+                }
             });
+
             const result = [];
-            Object.entries(map).forEach(([dir, fls]) => {
-                if (dir !== '') {
-                    result.push({ type: 'folder', name: dir, path: dir, files: fls });
-                } else {
-                    fls.forEach(f => result.push({ type: 'file', file: f }));
+            Object.entries(topLevelGroups).forEach(([topDir, fls]) => {
+                let commonParts = fls[0].relative_path.split('/').slice(0, -1);
+                for (let i = 1; i < fls.length; i++) {
+                    const parts = fls[i].relative_path.split('/').slice(0, -1);
+                    let j = 0;
+                    while (j < commonParts.length && j < parts.length && commonParts[j] === parts[j]) {
+                        j++;
+                    }
+                    commonParts.length = j;
                 }
+                const groupName = commonParts.join('/');
+                result.push({ type: 'folder', name: groupName, path: groupName, files: fls });
+            });
+
+            rootFiles.forEach(f => {
+                result.push({ type: 'file', file: f });
             });
+
             result.sort((a, b) => {
                 if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
                 return (a.name || a.file.name).localeCompare(b.name || b.file.name);
@@ -859,35 +904,33 @@
                     <span class="fg-arrow" id="fa_${gid}">${IC.chevron}</span>
                     <span class="fg-icon">${IC.folder}</span>
                     <span class="fg-name">${esc(g.name)}/</span>
-                    <span class="fg-count">${g.files.length}</span>
+                    <span class="fg-count">${g.files.length} 个文件</span>
                 </div>
-                <div class="col-text commit-tag" title="${esc(version.commit_id)}">${esc(version.commit_id.substring(0, 8))}</div>
-                <div class="col-text" title="${fmtTime(version.created_at)}">${relTime(version.created_at)}</div>
-                <div class="col-text">${version.author ? esc(version.author) : '-'}</div>
                 <div></div>
             </div>
             <div class="fg-children" id="${gid}">
-                ${g.files.map(f => fileRow(f, version, true)).join('')}
+                ${g.files.map(f => fileRow(f, version, true, g.path)).join('')}
             </div>`;
                 } else {
-                    h += fileRow(g.file, version, false);
+                    h += fileRow(g.file, version, false, null);
                 }
             });
             return h;
         }
 
-        function fileRow(f, version, isChild) {
+        function fileRow(f, version, isChild, groupPath) {
             const padding = isChild ? 'padding-left: 44px;' : '';
+            let displayName = f.name;
+            if (groupPath && f.relative_path.startsWith(groupPath + '/')) {
+                displayName = f.relative_path.substring(groupPath.length + 1);
+            }
             return `
     <div class="file-row" style="${padding}">
         <div class="file-name-col" title="${esc(f.relative_path)}">
             <span class="f-icon">${IC.file}</span>
-            <span class="f-name">${esc(f.name)}</span>
+            <span class="f-name">${esc(displayName)}</span>
             <span class="f-size">${fmtSize(f.file_size)}</span>
         </div>
-        <div class="col-text commit-tag" title="${esc(version.commit_id)}">${esc(version.commit_id.substring(0, 8))}</div>
-        <div class="col-text" title="${fmtTime(version.created_at)}">${relTime(version.created_at)}</div>
-        <div class="col-text">${version.author ? esc(version.author) : '-'}</div>
         <div class="btn-dl-wrap">
             <a class="btn-dl" href="/files/${f.id}/content" download="${esc(f.name)}" onclick="event.stopPropagation();">${IC.download}</a>
         </div>