|
@@ -4098,6 +4098,7 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
|
|
|
const matchType = noteEval['匹配类型'] || '未评估';
|
|
const matchType = noteEval['匹配类型'] || '未评估';
|
|
|
const explanation = noteEval['说明'] || noteEval['评分说明'] || '';
|
|
const explanation = noteEval['说明'] || noteEval['评分说明'] || '';
|
|
|
const firstLayerEval = noteEval['第一层评估'] || {{}};
|
|
const firstLayerEval = noteEval['第一层评估'] || {{}};
|
|
|
|
|
+ const secondLayerEval = noteEval['第二层评估'] || {{}};
|
|
|
|
|
|
|
|
// 根据匹配类型确定样式
|
|
// 根据匹配类型确定样式
|
|
|
let matchClass = '';
|
|
let matchClass = '';
|
|
@@ -4131,16 +4132,27 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
|
|
|
|
|
|
|
|
const evalDetailId = `eval-detail-${{featureIdx}}-${{baseWordIdx}}-${{swIdx}}-${{noteIdx}}`;
|
|
const evalDetailId = `eval-detail-${{featureIdx}}-${{baseWordIdx}}-${{swIdx}}-${{noteIdx}}`;
|
|
|
|
|
|
|
|
- // 检查是否有解构数据,且仅当完全匹配时显示解构按钮
|
|
|
|
|
|
|
+ // 检查是否有解构数据和相似度数据
|
|
|
|
|
+ const hasSimilarityInfo = !!similarityData[noteId];
|
|
|
|
|
+ let maxSimilarityScore = 0;
|
|
|
|
|
+ if (hasSimilarityInfo) {{
|
|
|
|
|
+ const features = similarityData[noteId].deconstructed_features || [];
|
|
|
|
|
+ if (features.length > 0) {{
|
|
|
|
|
+ maxSimilarityScore = Math.max(...features.map(f => f.similarity_score || 0));
|
|
|
|
|
+ }}
|
|
|
|
|
+ }}
|
|
|
const hasDeconstruction = deconstructionData[noteId] != null && matchType.includes('完全匹配');
|
|
const hasDeconstruction = deconstructionData[noteId] != null && matchType.includes('完全匹配');
|
|
|
|
|
|
|
|
|
|
+ const cardId = `card-${{featureIdx}}-${{baseWordIdx}}-${{swIdx}}-${{noteIdx}}`;
|
|
|
html += `
|
|
html += `
|
|
|
- <div class="note-card ${{matchClass}}" style="border:2px solid #fbbf24;border-radius:12px;overflow:hidden;background:white;transition:all 0.2s;cursor:pointer;" onclick="openNoteImagesModal(${{featureIdx}}, ${{baseWordIdx}}, ${{swIdx}}, ${{noteIdx}})">
|
|
|
|
|
|
|
+ <div class="note-card ${{matchClass}}" style="border:2px solid #fbbf24;border-radius:12px;overflow:hidden;background:white;transition:all 0.2s;cursor:pointer;" onclick="openAllNotesModal(` + featureIdx + `, ` + baseWordIdx + `, ` + swIdx + `, ` + noteIdx + `)">
|
|
|
<!-- 图片轮播区域 -->
|
|
<!-- 图片轮播区域 -->
|
|
|
- <div style="position:relative;width:100%;height:260px;background:#f3f4f6;">
|
|
|
|
|
- ${{cover ? `<img src="${{cover}}" style="width:100%;height:100%;object-fit:cover;">` : `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#9ca3af;">${{typeIcon}}</div>`}}
|
|
|
|
|
|
|
+ <div id="${{cardId}}" style="position:relative;width:100%;height:260px;background:#f3f4f6;" data-current-index="0" data-images='${{JSON.stringify(imageList)}}'>
|
|
|
|
|
+ <img src="${{imageList[0] || ''}}" style="width:100%;height:100%;object-fit:cover;" onerror="this.parentElement.innerHTML='<div style=\\"width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#9ca3af;\\">${{typeIcon}}</div>'">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图片计数器 -->
|
|
|
<div style="position:absolute;top:10px;right:10px;background:rgba(0,0,0,0.6);color:white;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600;">
|
|
<div style="position:absolute;top:10px;right:10px;background:rgba(0,0,0,0.6);color:white;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600;">
|
|
|
- 1/${{imageList.length || 1}}
|
|
|
|
|
|
|
+ <span id="${{cardId}}-counter">1</span>/${{imageList.length || 1}}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -4163,18 +4175,25 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 评分徽章 -->
|
|
<!-- 评分徽章 -->
|
|
|
- <div style="display:flex;align-items:center;justify-content:space-between;padding:8px;background:#f9fafb;border-radius:6px;margin-bottom:8px;">
|
|
|
|
|
- <span style="font-size:12px;color:${{matchColor}};font-weight:600;">
|
|
|
|
|
- ${{matchIcon}} ${{matchType}} (${{comprehensiveScore.toFixed(1)}}分)
|
|
|
|
|
- </span>
|
|
|
|
|
- <button onclick="event.stopPropagation(); toggleEvalDetail('${{evalDetailId}}')"
|
|
|
|
|
- style="font-size:11px;color:#667eea;background:none;border:none;cursor:pointer;padding:2px 6px;">
|
|
|
|
|
- 详情 ▼
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <div style="padding:8px;background:#f9fafb;border-radius:6px;margin-bottom:8px;">
|
|
|
|
|
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
|
|
|
|
|
+ <span style="font-size:12px;color:${{matchColor}};font-weight:600;">
|
|
|
|
|
+ ${{matchIcon}} 搜索结果与目标匹配: ${{comprehensiveScore.toFixed(2)}}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <button onclick="event.stopPropagation(); toggleEvalDetail('${{evalDetailId}}')"
|
|
|
|
|
+ style="font-size:11px;color:#667eea;background:none;border:none;cursor:pointer;padding:2px 6px;">
|
|
|
|
|
+ 详情 ▼
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ${{hasSimilarityInfo ? `
|
|
|
|
|
+ <div style="font-size:11px;color:#6b7280;margin-top:4px;">
|
|
|
|
|
+ 解构结果与目标匹配: ${{maxSimilarityScore.toFixed(2)}}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ` : ''}}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 评估详情(可展开) -->
|
|
<!-- 评估详情(可展开) -->
|
|
|
- <div id="${{evalDetailId}}" style="display:none;font-size:11px;color:#6b7280;padding:8px;background:#fef3c7;border-radius:6px;margin-top:8px;max-height:150px;overflow-y:auto;">
|
|
|
|
|
|
|
+ <div id="${{evalDetailId}}" style="display:none;font-size:11px;color:#6b7280;padding:8px;background:#fef3c7;border-radius:6px;margin-top:8px;max-height:200px;overflow-y:auto;">
|
|
|
<div style="margin-bottom:6px;">
|
|
<div style="margin-bottom:6px;">
|
|
|
<strong style="color:#92400e;">Query相关性:</strong> ${{queryRelevance}}
|
|
<strong style="color:#92400e;">Query相关性:</strong> ${{queryRelevance}}
|
|
|
</div>
|
|
</div>
|
|
@@ -4184,8 +4203,21 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
|
|
|
</div>
|
|
</div>
|
|
|
` : ''}}
|
|
` : ''}}
|
|
|
${{firstLayerEval['说明'] ? `
|
|
${{firstLayerEval['说明'] ? `
|
|
|
|
|
+ <div style="margin-bottom:6px;">
|
|
|
|
|
+ <strong style="color:#92400e;">第一层评估(与query比较):</strong> ${{firstLayerEval['说明']}}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ` : ''}}
|
|
|
|
|
+ ${{secondLayerEval['评分说明'] ? `
|
|
|
|
|
+ <div style="margin-bottom:6px;">
|
|
|
|
|
+ <strong style="color:#92400e;">第二层评估(与目标比较):</strong> ${{secondLayerEval['评分说明']}}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ` : ''}}
|
|
|
|
|
+ ${{secondLayerEval['关键匹配点'] && secondLayerEval['关键匹配点'].length > 0 ? `
|
|
|
<div>
|
|
<div>
|
|
|
- <strong style="color:#92400e;">第一层评估:</strong> ${{firstLayerEval['说明']}}
|
|
|
|
|
|
|
+ <strong style="color:#92400e;">关键匹配点:</strong>
|
|
|
|
|
+ <ul style="margin:4px 0 0 0;padding-left:20px;">
|
|
|
|
|
+ ${{secondLayerEval['关键匹配点'].map(point => `<li>${{point}}</li>`).join('')}}
|
|
|
|
|
+ </ul>
|
|
|
</div>
|
|
</div>
|
|
|
` : ''}}
|
|
` : ''}}
|
|
|
</div>
|
|
</div>
|
|
@@ -5158,104 +5190,154 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
|
|
|
}});
|
|
}});
|
|
|
}}
|
|
}}
|
|
|
|
|
|
|
|
- // ========== 新增功能2: 帖子图片浮层模态窗口 ==========
|
|
|
|
|
- function openNoteImagesModal(featureIdx, groupIdx, searchIdx, noteIdx) {{
|
|
|
|
|
- console.log('🎯 [点击事件] 参数 - featureIdx:', featureIdx, 'groupIdx:', groupIdx, 'searchIdx:', searchIdx, 'noteIdx:', noteIdx);
|
|
|
|
|
|
|
+ // ========== 卡片图片切换功能 ==========
|
|
|
|
|
+ function changeCardImage(cardId, direction) {{
|
|
|
|
|
+ const card = document.getElementById(cardId);
|
|
|
|
|
+ if (!card) return;
|
|
|
|
|
|
|
|
- const feature = data[featureIdx];
|
|
|
|
|
- console.log('📊 [数据检查] feature:', feature);
|
|
|
|
|
|
|
+ const images = JSON.parse(card.getAttribute('data-images') || '[]');
|
|
|
|
|
+ if (images.length <= 1) return;
|
|
|
|
|
|
|
|
- const group = feature['组合评估结果_分组'][groupIdx];
|
|
|
|
|
- console.log('📊 [数据检查] group:', group);
|
|
|
|
|
|
|
+ let currentIndex = parseInt(card.getAttribute('data-current-index') || '0');
|
|
|
|
|
+ currentIndex = (currentIndex + direction + images.length) % images.length;
|
|
|
|
|
+
|
|
|
|
|
+ // 更新图片
|
|
|
|
|
+ const img = card.querySelector('img');
|
|
|
|
|
+ if (img) {{
|
|
|
|
|
+ img.src = images[currentIndex];
|
|
|
|
|
+ }}
|
|
|
|
|
+
|
|
|
|
|
+ // 更新索引
|
|
|
|
|
+ card.setAttribute('data-current-index', currentIndex);
|
|
|
|
|
+
|
|
|
|
|
+ // 更新计数器
|
|
|
|
|
+ const counter = document.getElementById(cardId + '-counter');
|
|
|
|
|
+ if (counter) {{
|
|
|
|
|
+ counter.textContent = currentIndex + 1;
|
|
|
|
|
+ }}
|
|
|
|
|
+ }}
|
|
|
|
|
|
|
|
|
|
+ // ========== 新增功能2: 帖子图片浮层模态窗口(显示当前帖子的所有图片平铺) ==========
|
|
|
|
|
+ function openAllNotesModal(featureIdx, groupIdx, searchIdx, initialNoteIdx) {{
|
|
|
|
|
+ const feature = data[featureIdx];
|
|
|
|
|
+ const group = feature['组合评估结果_分组'][groupIdx];
|
|
|
const search = group['top10_searches'][searchIdx];
|
|
const search = group['top10_searches'][searchIdx];
|
|
|
- console.log('📊 [数据检查] search:', search);
|
|
|
|
|
|
|
+ const searchWord = search.search_word || '搜索词';
|
|
|
|
|
|
|
|
const searchResult = search.search_result || {{}};
|
|
const searchResult = search.search_result || {{}};
|
|
|
- console.log('📊 [数据检查] searchResult:', searchResult);
|
|
|
|
|
-
|
|
|
|
|
const notes = searchResult.data?.data || [];
|
|
const notes = searchResult.data?.data || [];
|
|
|
- console.log('📊 [数据检查] notes数组长度:', notes.length);
|
|
|
|
|
-
|
|
|
|
|
- const note = notes[noteIdx];
|
|
|
|
|
- console.log('📊 [数据检查] note:', note);
|
|
|
|
|
|
|
+ const note = notes[initialNoteIdx];
|
|
|
|
|
|
|
|
if (!note) {{
|
|
if (!note) {{
|
|
|
- console.log('❌ [浮层] 找不到帖子数据');
|
|
|
|
|
|
|
+ console.log('❌ 找不到帖子数据');
|
|
|
return;
|
|
return;
|
|
|
}}
|
|
}}
|
|
|
|
|
|
|
|
const card = note.note_card || {{}};
|
|
const card = note.note_card || {{}};
|
|
|
- console.log('📊 [数据检查] note_card:', card);
|
|
|
|
|
-
|
|
|
|
|
const images = card.image_list || [];
|
|
const images = card.image_list || [];
|
|
|
- console.log('📊 [数据检查] image_list:', images);
|
|
|
|
|
- console.log('📊 [数据检查] image_list类型:', typeof images, 'isArray:', Array.isArray(images));
|
|
|
|
|
-
|
|
|
|
|
const title = card.display_title || '无标题';
|
|
const title = card.display_title || '无标题';
|
|
|
const noteId = note.id || '';
|
|
const noteId = note.id || '';
|
|
|
|
|
|
|
|
- console.log('🔍 [帖子图片浮层] 帖子ID:', noteId);
|
|
|
|
|
- console.log('🔍 [帖子图片浮层] 标题:', title);
|
|
|
|
|
- console.log('🔍 [帖子图片浮层] 图片数量:', images.length);
|
|
|
|
|
- console.log('🔍 [帖子图片浮层] 图片数组内容:', images);
|
|
|
|
|
-
|
|
|
|
|
if (images.length === 0) {{
|
|
if (images.length === 0) {{
|
|
|
- console.log('❌ [浮层] 该帖子没有图片');
|
|
|
|
|
- console.log('❌ [浮层] card完整内容:', JSON.stringify(card, null, 2));
|
|
|
|
|
|
|
+ console.log('❌ 该帖子没有图片');
|
|
|
return;
|
|
return;
|
|
|
}}
|
|
}}
|
|
|
|
|
|
|
|
|
|
+ // 显示模态窗口
|
|
|
const modal = document.getElementById('notesModal');
|
|
const modal = document.getElementById('notesModal');
|
|
|
const modalContent = document.getElementById('notesModalContent');
|
|
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>
|
|
|
|
|
|
|
+ // 生成所有图片的HTML(网格布局,自适应高度)
|
|
|
|
|
+ let imagesHtml = '';
|
|
|
|
|
+ images.forEach((imageUrl, imgIdx) => {{
|
|
|
|
|
+ imagesHtml += `
|
|
|
|
|
+ <div style="position: relative; background: #2a2a2a; border-radius: 8px; overflow: hidden;">
|
|
|
|
|
+ <img src="${{imageUrl}}"
|
|
|
|
|
+ alt="图片 ${{imgIdx + 1}}"
|
|
|
|
|
+ style="width: 100%; height: auto; display: block; cursor: zoom-in;"
|
|
|
|
|
+ onclick="openFullscreenImage('${{imageUrl}}'); event.stopPropagation();">
|
|
|
|
|
+ <div style="position: absolute; bottom: 8px; left: 8px; padding: 4px 8px; background: rgba(0,0,0,0.7); color: white; font-size: 11px; border-radius: 4px;">
|
|
|
|
|
+ ${{imgIdx + 1}}/${{images.length}}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
`;
|
|
`;
|
|
|
- }}
|
|
|
|
|
|
|
+ }});
|
|
|
|
|
|
|
|
- // 构建图片网格HTML
|
|
|
|
|
- let imagesHtml = '<div class="notes-grid-modal">';
|
|
|
|
|
|
|
+ modalContent.innerHTML = `
|
|
|
|
|
+ <div style="position: relative; width: 100%; height: 100%; display: flex; flex-direction: column;">
|
|
|
|
|
+ <!-- 关闭按钮(右上角) -->
|
|
|
|
|
+ <button onclick="closeNotesModal(); event.stopPropagation();"
|
|
|
|
|
+ class="modal-close-btn"
|
|
|
|
|
+ style="position: absolute; top: 20px; right: 20px; z-index: 1000;
|
|
|
|
|
+ width: 40px; height: 40px; background: rgba(0,0,0,0.7); color: white; border: none; border-radius: 50%;
|
|
|
|
|
+ cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center;
|
|
|
|
|
+ transition: all 0.3s;"
|
|
|
|
|
+ onmouseover="this.style.background='rgba(0,0,0,0.9)'"
|
|
|
|
|
+ onmouseout="this.style.background='rgba(0,0,0,0.7)'">
|
|
|
|
|
+ ✕
|
|
|
|
|
+ </button>
|
|
|
|
|
|
|
|
- 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 style="padding: 20px 70px 20px 20px; background: rgba(0,0,0,0.9); color: white; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
|
|
|
|
+ <div style="font-size: 14px; opacity: 0.7; margin-bottom: 8px;">🔍 ${{searchWord}}</div>
|
|
|
|
|
+ <div style="font-size: 18px; font-weight: 600; margin-bottom: 5px;">📷 ${{title}}</div>
|
|
|
|
|
+ <div style="font-size: 14px; opacity: 0.8;">
|
|
|
|
|
+ 共 ${{images.length}} 张图片
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- `;
|
|
|
|
|
- }});
|
|
|
|
|
|
|
|
|
|
- imagesHtml += '</div>';
|
|
|
|
|
|
|
+ <!-- 图片网格容器 -->
|
|
|
|
|
+ <div style="flex: 1; background: #1a1a1a; overflow-y: auto; padding: 20px; display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 15px; align-content: start;">
|
|
|
|
|
+ ${{imagesHtml}}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ `;
|
|
|
|
|
|
|
|
- modalContent.innerHTML = imagesHtml;
|
|
|
|
|
modal.classList.add('active');
|
|
modal.classList.add('active');
|
|
|
|
|
|
|
|
// 点击背景关闭
|
|
// 点击背景关闭
|
|
|
modal.onclick = function(e) {{
|
|
modal.onclick = function(e) {{
|
|
|
- if (e.target === modal) {{
|
|
|
|
|
|
|
+ if (e.target === modal || e.target.classList.contains('modal-close-btn')) {{
|
|
|
closeNotesModal();
|
|
closeNotesModal();
|
|
|
}}
|
|
}}
|
|
|
}};
|
|
}};
|
|
|
}}
|
|
}}
|
|
|
|
|
|
|
|
|
|
+ // 全屏查看图片
|
|
|
|
|
+ function openFullscreenImage(imageUrl) {{
|
|
|
|
|
+ const fullscreenDiv = document.createElement('div');
|
|
|
|
|
+ fullscreenDiv.style.cssText = `
|
|
|
|
|
+ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
|
|
|
|
+ background: rgba(0,0,0,0.95); z-index: 10000;
|
|
|
|
|
+ display: flex; align-items: center; justify-content: center;
|
|
|
|
|
+ cursor: zoom-out;
|
|
|
|
|
+ `;
|
|
|
|
|
+
|
|
|
|
|
+ const img = document.createElement('img');
|
|
|
|
|
+ img.src = imageUrl;
|
|
|
|
|
+ img.style.cssText = 'max-width: 95%; max-height: 95%; object-fit: contain;';
|
|
|
|
|
+
|
|
|
|
|
+ fullscreenDiv.appendChild(img);
|
|
|
|
|
+ document.body.appendChild(fullscreenDiv);
|
|
|
|
|
+
|
|
|
|
|
+ fullscreenDiv.onclick = function() {{
|
|
|
|
|
+ document.body.removeChild(fullscreenDiv);
|
|
|
|
|
+ }};
|
|
|
|
|
+ }}
|
|
|
|
|
+
|
|
|
function closeNotesModal() {{
|
|
function closeNotesModal() {{
|
|
|
const modal = document.getElementById('notesModal');
|
|
const modal = document.getElementById('notesModal');
|
|
|
modal.classList.remove('active');
|
|
modal.classList.remove('active');
|
|
|
}}
|
|
}}
|
|
|
|
|
|
|
|
|
|
+ // 键盘事件:ESC关闭浮层
|
|
|
|
|
+ document.addEventListener('keydown', function(e) {{
|
|
|
|
|
+ const modal = document.getElementById('notesModal');
|
|
|
|
|
+ if (modal.classList.contains('active') && e.key === 'Escape') {{
|
|
|
|
|
+ closeNotesModal();
|
|
|
|
|
+ }}
|
|
|
|
|
+ }});
|
|
|
|
|
+
|
|
|
// ========== 新增功能3: 右侧滚动与左侧联动 ==========
|
|
// ========== 新增功能3: 右侧滚动与左侧联动 ==========
|
|
|
// 使用Intersection Observer监听右侧内容块的可见性
|
|
// 使用Intersection Observer监听右侧内容块的可见性
|
|
|
const observerOptions = {{
|
|
const observerOptions = {{
|