Explorar o código

可视化修改

刘立冬 hai 3 semanas
pai
achega
6c76823530
Modificáronse 1 ficheiros con 370 adicións e 2 borrados
  1. 370 2
      visualize_stage78_with_deconstruction.py

+ 370 - 2
visualize_stage78_with_deconstruction.py

@@ -753,6 +753,232 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             font-weight: 600;
         }}
 
+        /* ========== 新增: 顶部统计面板折叠样式 ========== */
+        .stats-panel {{
+            position: relative;
+            transition: all 0.3s ease;
+            cursor: pointer;
+        }}
+
+        .stats-panel.collapsed {{
+            max-height: 90px;
+            overflow: hidden;
+        }}
+
+        .stats-panel.collapsed .stats-row:not(:first-child) {{
+            display: none;
+        }}
+
+        .stats-toggle-icon {{
+            position: absolute;
+            right: 30px;
+            top: 50%;
+            transform: translateY(-50%);
+            font-size: 24px;
+            color: rgba(255, 255, 255, 0.9);
+            transition: transform 0.3s ease;
+            cursor: pointer;
+            z-index: 100;
+        }}
+
+        .stats-panel.collapsed .stats-toggle-icon {{
+            transform: translateY(-50%) rotate(180deg);
+        }}
+
+        /* ========== 新增: 帖子浮层模态窗口样式 ========== */
+        .notes-modal {{
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(0, 0, 0, 0.8);
+            z-index: 10001;
+            animation: fadeIn 0.3s ease;
+        }}
+
+        .notes-modal.active {{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }}
+
+        .notes-modal-window {{
+            background: white;
+            border-radius: 12px;
+            width: 95%;
+            max-width: 1400px;
+            height: 90vh;
+            overflow: hidden;
+            display: flex;
+            flex-direction: column;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+            animation: slideUp 0.3s ease;
+        }}
+
+        .notes-modal-header {{
+            padding: 20px 25px;
+            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+            color: white;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            flex-shrink: 0;
+        }}
+
+        .notes-modal-title {{
+            font-size: 18px;
+            font-weight: 600;
+        }}
+
+        .notes-modal-subtitle {{
+            font-size: 14px;
+            opacity: 0.9;
+            margin-top: 5px;
+        }}
+
+        .notes-modal-close-btn {{
+            background: rgba(255, 255, 255, 0.2);
+            border: none;
+            color: white;
+            width: 36px;
+            height: 36px;
+            border-radius: 50%;
+            cursor: pointer;
+            font-size: 20px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s;
+        }}
+
+        .notes-modal-close-btn:hover {{
+            background: rgba(255, 255, 255, 0.3);
+            transform: scale(1.1);
+        }}
+
+        .notes-modal-body {{
+            flex: 1;
+            overflow-y: auto;
+            padding: 25px;
+            background: #fafbfc;
+        }}
+
+        .notes-grid-modal {{
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+            gap: 20px;
+        }}
+
+        .note-card-modal {{
+            background: white;
+            border-radius: 12px;
+            overflow: hidden;
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+            cursor: pointer;
+            transition: all 0.3s ease;
+            display: flex;
+            flex-direction: column;
+        }}
+
+        .note-card-modal:hover {{
+            transform: translateY(-4px);
+            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
+        }}
+
+        .note-image-wrapper {{
+            position: relative;
+            width: 100%;
+            height: 200px;
+            background: #f3f4f6;
+            overflow: hidden;
+        }}
+
+        .note-image {{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+        }}
+
+        .note-image-placeholder {{
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 48px;
+            background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
+        }}
+
+        .note-content {{
+            padding: 15px;
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+        }}
+
+        .note-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #111827;
+            line-height: 1.4;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+        }}
+
+        .note-score {{
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            font-size: 13px;
+        }}
+
+        .score-label {{
+            color: #6b7280;
+        }}
+
+        .score-value {{
+            font-weight: 700;
+            padding: 2px 8px;
+            border-radius: 4px;
+        }}
+
+        .score-value.high {{
+            color: #10b981;
+            background: #d1fae5;
+        }}
+
+        .score-value.medium {{
+            color: #f59e0b;
+            background: #fef3c7;
+        }}
+
+        .score-value.low {{
+            color: #6b7280;
+            background: #e5e7eb;
+        }}
+
+        @keyframes fadeIn {{
+            from {{ opacity: 0; }}
+            to {{ opacity: 1; }}
+        }}
+
+        @keyframes slideUp {{
+            from {{
+                opacity: 0;
+                transform: translateY(50px);
+            }}
+            to {{
+                opacity: 1;
+                transform: translateY(0);
+            }}
+        }}
+
         /* 右侧结果区 */
         .right-content {{
             flex: 1;
@@ -1447,7 +1673,8 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
 </head>
 <body>
     <!-- 统计面板 -->
-    <div class="stats-panel">
+    <div class="stats-panel collapsed">
+        <div class="stats-toggle-icon">▼</div>
         <div class="stats-container">
             <div class="stats-row">
                 <div class="stat-item">
@@ -1557,6 +1784,17 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
         </div>
     </div>
 
+    <!-- 帖子浮层模态窗口 -->
+    <div class="notes-modal" id="notesModal">
+        <div class="notes-modal-window">
+            <div class="notes-modal-header">
+                <div class="notes-modal-title">📝 搜索词帖子列表</div>
+                <button class="notes-modal-close-btn" onclick="closeNotesModal()">×</button>
+            </div>
+            <div class="notes-modal-body" id="notesModalContent"></div>
+        </div>
+    </div>
+
     <script>
         // 数据
         const data = {data_json};
@@ -1879,7 +2117,7 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             }}
 
             return `
-                <div class="note-card ${{evalClass}}" data-eval-category="${{evalCategory}}" onclick="openNote('${{noteId}}')">
+                <div class="note-card ${{evalClass}}" data-eval-category="${{evalCategory}}" onclick="openNoteImagesModal(${{featureIdx}}, ${{groupIdx}}, ${{swIdx}}, ${{noteIdx}})">
                     <div class="image-carousel" id="${{carouselId}}">
                         <div class="carousel-images">
                             ${{images.map(img => `<img class="carousel-image" src="${{img}}" alt="帖子图片" loading="lazy">`).join('')}}
@@ -2447,6 +2685,136 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
                 console.error('❌ [错误] 初始化失败:', error);
             }}
         }});
+
+        // ========== 新增功能1: 统计面板折叠/展开 ==========
+        const statsPanel = document.querySelector('.stats-panel');
+        if (statsPanel) {{
+            statsPanel.addEventListener('click', function() {{
+                this.classList.toggle('collapsed');
+            }});
+        }}
+
+        // ========== 新增功能2: 帖子图片浮层模态窗口 ==========
+        function openNoteImagesModal(featureIdx, groupIdx, searchIdx, noteIdx) {{
+            const feature = data[featureIdx];
+            const group = feature['组合评估结果_分组'][groupIdx];
+            const search = group['top10_searches'][searchIdx];
+            const searchResult = search.search_result || {{}};
+            const notes = searchResult.data?.data || [];
+            const note = notes[noteIdx];
+
+            if (!note) {{
+                console.log('❌ [浮层] 找不到帖子数据');
+                return;
+            }}
+
+            const card = note.note_card || {{}};
+            const images = card.image_list || [];
+            const title = card.display_title || '无标题';
+            const noteId = note.id || '';
+
+            console.log('🔍 [帖子图片浮层] 帖子ID:', noteId);
+            console.log('🔍 [帖子图片浮层] 标题:', title);
+            console.log('🔍 [帖子图片浮层] 图片数量:', images.length);
+
+            if (images.length === 0) {{
+                console.log('❌ [浮层] 该帖子没有图片');
+                return;
+            }}
+
+            const modal = document.getElementById('notesModal');
+            const modalContent = document.getElementById('notesModalContent');
+
+            // 更新模态窗口标题
+            const modalTitle = document.querySelector('.notes-modal-title');
+            if (modalTitle) {{
+                modalTitle.innerHTML = `
+                    <div>
+                        <div style="font-size: 18px; font-weight: 600;">📷 ${{title}}</div>
+                        <div style="font-size: 14px; opacity: 0.9; margin-top: 5px;">共 ${{images.length}} 张图片</div>
+                    </div>
+                `;
+            }}
+
+            // 构建图片网格HTML
+            let imagesHtml = '<div class="notes-grid-modal">';
+
+            images.forEach((imageUrl, idx) => {{
+                imagesHtml += `
+                    <div class="note-card-modal" onclick="window.open('https://www.xiaohongshu.com/explore/${{noteId}}', '_blank'); event.stopPropagation();">
+                        <div class="note-image-wrapper" style="height: 300px;">
+                            <img src="${{imageUrl}}" alt="图片 ${{idx + 1}}" class="note-image" style="object-fit: contain; background: #000;"/>
+                        </div>
+                        <div class="note-content">
+                            <div style="text-align: center; color: #6b7280; font-size: 13px;">
+                                第 ${{idx + 1}} / ${{images.length}} 张
+                            </div>
+                        </div>
+                    </div>
+                `;
+            }});
+
+            imagesHtml += '</div>';
+
+            modalContent.innerHTML = imagesHtml;
+            modal.classList.add('active');
+
+            // 点击背景关闭
+            modal.onclick = function(e) {{
+                if (e.target === modal) {{
+                    closeNotesModal();
+                }}
+            }};
+        }}
+
+        function closeNotesModal() {{
+            const modal = document.getElementById('notesModal');
+            modal.classList.remove('active');
+        }}
+
+        // ========== 新增功能3: 右侧滚动与左侧联动 ==========
+        // 使用Intersection Observer监听右侧内容块的可见性
+        const observerOptions = {{
+            root: null,
+            rootMargin: '-20% 0px -70% 0px',  // 当元素在视口上20%位置时触发
+            threshold: 0
+        }};
+
+        const observer = new IntersectionObserver((entries) => {{
+            entries.forEach(entry => {{
+                if (entry.isIntersecting) {{
+                    const blockId = entry.target.id;
+                    const leftItem = document.getElementById('search-' + blockId.replace('block-', ''));
+
+                    if (leftItem) {{
+                        // 移除所有active状态
+                        document.querySelectorAll('.search-word-item').forEach(item => {{
+                            item.classList.remove('active');
+                        }});
+
+                        // 添加active到当前项
+                        leftItem.classList.add('active');
+
+                        // 滚动到可见区域
+                        leftItem.scrollIntoView({{
+                            behavior: 'smooth',
+                            block: 'nearest'
+                        }});
+                    }}
+                }}
+            }});
+        }}, observerOptions);
+
+        // 观察所有result-block
+        document.addEventListener('DOMContentLoaded', () => {{
+            setTimeout(() => {{
+                const blocks = document.querySelectorAll('.result-block');
+                blocks.forEach(block => {{
+                    observer.observe(block);
+                }});
+                console.log('✅ [系统] 已设置', blocks.length, '个内容块的滚动监听');
+            }}, 1000);
+        }});
     </script>
 </body>
 </html>