|
|
@@ -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>
|