Bläddra i källkod

feat: 优化灵感点可视化界面的交互和样式

主要改进:
1. 视觉优化
   - 增加区块间距,使布局更简洁直观
   - 相同部分使用绿色背景,增量部分使用黄色背景
   - 分数说明改用黄色背景突出显示
   - 大标题使用浅紫色渐变,步骤1用浅黄色,步骤2用浅绿色

2. 步骤流程展示
   - 添加步骤1(灵感点匹配灵感分类)和步骤2(搜索)的明确标识
   - 步骤2.1显示"直接搜索灵感分类",预留扩展空间
   - 步骤1和步骤2支持折叠/展开功能
   - 默认只展开第一个匹配项,其他折叠

3. 搜索功能增强
   - 修复搜索结果匹配问题(从JSON读取真实keyword而非文件名)
   - 添加搜索参数展示(关键词、内容类型、排序方式、发布时间)
   - 添加搜索结果统计(找到的内容总数)
   - 显示所有搜索结果,不再截断

4. 卡片样式统一
   - 统一所有帖子卡片样式(灵感点详情、分类详情、搜索结果)
   - 底部布局:左侧用户名,右侧点赞/评论数
   - "在小红书查看"按钮移至详情弹窗中

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yangxiaohui 2 veckor sedan
förälder
incheckning
cce2b964cb
1 ändrade filer med 305 tillägg och 51 borttagningar
  1. 305 51
      visualize_inspiration_points.py

+ 305 - 51
visualize_inspiration_points.py

@@ -63,12 +63,17 @@ def load_inspiration_points_data(inspiration_dir: str) -> List[Dict[str, Any]]:
                     if search_dir.exists() and search_dir.is_dir():
                         search_files = list(search_dir.glob("all_search_*.json"))
                         for search_file in search_files:
-                            # 从文件名提取关键词:all_search_关键词.json
-                            keyword = search_file.stem.replace("all_search_", "")
                             try:
                                 with open(search_file, 'r', encoding='utf-8') as f:
                                     search_data = json.load(f)
-                                    search_results[keyword] = search_data
+                                    # 从JSON内容中读取真实的keyword,而不是从文件名提取
+                                    keyword = search_data.get("search_params", {}).get("keyword", "")
+                                    if keyword:
+                                        search_results[keyword] = search_data
+                                    else:
+                                        # 如果JSON中没有keyword,则从文件名提取
+                                        keyword = search_file.stem.replace("all_search_", "")
+                                        search_results[keyword] = search_data
                             except Exception as e:
                                 print(f"警告: 读取 {search_file} 失败: {e}")
 
@@ -234,13 +239,16 @@ def generate_post_card_html(post: Dict[str, Any], note_id_prefix: str = "info-po
     card_html = f'''
     <div class="search-note-item" data-note-data='{note_data_json_escaped}' onclick="showNoteDetail(this)">
         {images_html}
-        <div class="note-info">
+        <div class="note-content">
             <div class="note-title">{html_module.escape(title) if title else "无标题"}</div>
-            <div class="note-stats">
-                <span class="note-stat">❤️ {like_count if like_count else 0}</span>
-                {f'<span class="note-stat">💬 {comment_count}</span>' if comment_count else ''}
+            <div class="note-desc">{html_module.escape(desc) if desc else "暂无描述"}</div>
+            <div class="note-footer">
+                <div class="note-author">@{html_module.escape(author) if author else "匿名"}</div>
+                <div class="note-stats">
+                    <span>👍 {like_count if like_count else 0}</span>
+                    {f'<span>💬 {comment_count}</span>' if comment_count else ''}
+                </div>
             </div>
-            <div class="note-author">{html_module.escape(author) if author else "匿名"}</div>
         </div>
         {f'<div class="note-points-hover">{points_html}</div>' if points_html else ''}
     </div>
@@ -427,12 +435,55 @@ def generate_inspiration_card_html(
             search_html = ""
             if element_name in search_results:
                 search_data = search_results[element_name]
+                search_params = search_data.get("search_params", {})
                 notes = search_data.get("notes", [])
                 notes_count = len(notes)
 
+                # 生成搜索参数HTML
+                search_params_html = ""
+                if search_params:
+                    keyword = search_params.get("keyword", "")
+                    content_type = search_params.get("content_type", "不限")
+                    sort_type = search_params.get("sort_type", "综合")
+                    publish_time = search_params.get("publish_time", "不限")
+
+                    search_params_html = f'''
+                    <div class="search-params-section">
+                        <div class="search-params-title">🔍 搜索参数</div>
+                        <div class="search-params-grid">
+                            <div class="search-param-item">
+                                <span class="search-param-label">关键词:</span>
+                                <span class="search-param-value">{html_module.escape(keyword)}</span>
+                            </div>
+                            <div class="search-param-item">
+                                <span class="search-param-label">内容类型:</span>
+                                <span class="search-param-value">{html_module.escape(content_type)}</span>
+                            </div>
+                            <div class="search-param-item">
+                                <span class="search-param-label">排序方式:</span>
+                                <span class="search-param-value">{html_module.escape(sort_type)}</span>
+                            </div>
+                            <div class="search-param-item">
+                                <span class="search-param-label">发布时间:</span>
+                                <span class="search-param-value">{html_module.escape(publish_time)}</span>
+                            </div>
+                        </div>
+                    </div>
+                    '''
+
+                # 生成搜索结果统计HTML
+                search_summary_html = f'''
+                <div class="search-summary-section">
+                    <div class="search-summary-title">📊 搜索结果</div>
+                    <div class="search-summary-content">
+                        共找到 <span class="search-result-count">{notes_count}</span> 条相关内容
+                    </div>
+                </div>
+                '''
+
                 if notes_count > 0:
                     notes_items = ""
-                    for note_idx, note in enumerate(notes[:20]):  # 显示前20条
+                    for note_idx, note in enumerate(notes):  # 显示所有结果
                         title = note.get("title", "")
                         desc = note.get("desc", "")
                         link = note.get("link", "")
@@ -506,21 +557,30 @@ def generate_inspiration_card_html(
                                         <span>💬 {comment_count}</span>
                                     </div>
                                 </div>
-                                <a href="{link}" target="_blank" class="note-link" onclick="event.stopPropagation()">在小红书查看</a>
                             </div>
                         </div>
                         '''
 
                     search_html = f'''
                     <div class="search-results-section">
+                        {search_params_html}
+                        {search_summary_html}
                         <div class="search-notes-list">
                             {notes_items}
                         </div>
                     </div>
                     '''
+                else:
+                    # 没有搜索结果时也显示参数
+                    search_html = f'''
+                    <div class="search-results-section">
+                        {search_params_html}
+                        {search_summary_html}
+                    </div>
+                    '''
 
-            # 默认全部展开
-            expanded_class = " expanded"
+            # 只有第一个匹配项默认展开
+            expanded_class = " expanded" if idx == 0 else ""
 
             # 生成当前匹配项的灵感点和灵感分类详情
             match_info_section = ""
@@ -595,21 +655,26 @@ def generate_inspiration_card_html(
 
             # 步骤1:灵感点匹配灵感分类
             step1_html = f'''
-            <div class="step-section-wrapper step-1-wrapper">
-                <div class="step-header">
-                    <span class="step-number-badge">步骤 1</span>
-                    <span class="step-title">灵感点匹配灵感分类</span>
+            <div class="step-section-wrapper step-1-wrapper expanded">
+                <div class="step-header" onclick="toggleStepWrapper(this)">
+                    <div class="step-header-content">
+                        <span class="step-number-badge">步骤 1</span>
+                        <span class="step-title">灵感点匹配灵感分类</span>
+                    </div>
+                    <div class="step-toggle">▼</div>
                 </div>
-                <div class="match-analysis-section" id="match-{idx}-step1" data-step-name="灵感点匹配灵感分类">
-                    <div class="match-parts-container">
-                        <div class="match-parts-column">
-                            {same_parts_html}
-                        </div>
-                        <div class="match-parts-column">
-                            {increment_parts_html}
+                <div class="step-wrapper-content">
+                    <div class="match-analysis-section" id="match-{idx}-step1" data-step-name="灵感点匹配灵感分类">
+                        <div class="match-parts-container">
+                            <div class="match-parts-column">
+                                {same_parts_html}
+                            </div>
+                            <div class="match-parts-column">
+                                {increment_parts_html}
+                            </div>
                         </div>
+                        {f'<div class="match-explain"><div class="match-explain-title">💡 分数说明</div><div class="match-explain-text">{html_module.escape(score_explain)}</div></div>' if score_explain else ''}
                     </div>
-                    {f'<div class="match-explain"><div class="match-explain-title">💡 分数说明</div><div class="match-explain-text">{html_module.escape(score_explain)}</div></div>' if score_explain else ''}
                 </div>
             </div>
             '''
@@ -618,16 +683,27 @@ def generate_inspiration_card_html(
             step2_html = ""
             if search_html:
                 step2_html = f'''
-                <div class="step-section expanded" data-step="2" id="match-{idx}-step2" data-step-name="灵感分类搜索">
-                    <div class="step-section-header" onclick="toggleStep(this)">
-                        <div class="step-section-title">
-                            <span class="step-number">2</span>
-                            <span>灵感分类搜索</span>
+                <div class="step-section-wrapper step-2-wrapper expanded">
+                    <div class="step-header" onclick="toggleStepWrapper(this)">
+                        <div class="step-header-content">
+                            <span class="step-number-badge">步骤 2</span>
+                            <span class="step-title">搜索</span>
                         </div>
                         <div class="step-toggle">▼</div>
                     </div>
-                    <div class="step-section-content">
-                        {search_html}
+                    <div class="step-wrapper-content">
+                        <div class="step-section expanded" data-step="2" id="match-{idx}-step2" data-step-name="灵感分类搜索">
+                            <div class="step-section-header" onclick="toggleStep(this)">
+                                <div class="step-section-title">
+                                    <span class="step-sub-number">2.1</span>
+                                    <span>直接搜索灵感分类</span>
+                                </div>
+                                <div class="step-toggle">▼</div>
+                            </div>
+                            <div class="step-section-content">
+                                {search_html}
+                            </div>
+                        </div>
                     </div>
                 </div>
                 '''
@@ -1024,6 +1100,12 @@ def generate_detail_modal_content_js() -> str:
         matchItem.classList.toggle('expanded');
     }
 
+    // 切换步骤wrapper的展开/折叠
+    function toggleStepWrapper(element) {
+        const stepWrapper = element.closest('.step-section-wrapper');
+        stepWrapper.classList.toggle('expanded');
+    }
+
     // 切换步骤的展开/折叠
     function toggleStep(element) {
         const stepSection = element.closest('.step-section');
@@ -1720,8 +1802,8 @@ def generate_html(
 
         .detail-label {{
             font-size: 12px;
-            color: #6366f1;
-            background: #eef2ff;
+            color: #5b21b6;
+            background: rgba(109, 40, 217, 0.15);
             padding: 3px 10px;
             border-radius: 4px;
             font-weight: 500;
@@ -2071,8 +2153,8 @@ def generate_html(
         }}
 
         .match-main-header {{
-            background: #ffffff;
-            border-bottom: 1px solid #e5e7eb;
+            background: linear-gradient(135deg, #a5b4fc 0%, #c4b5fd 100%);
+            border-bottom: none;
             padding: 16px 20px;
             cursor: pointer;
             display: flex;
@@ -2081,7 +2163,7 @@ def generate_html(
         }}
 
         .match-main-header:hover {{
-            background: #f9fafb;
+            background: linear-gradient(135deg, #8b9cfc 0%, #b39dfc 100%);
         }}
 
         .match-header-row {{
@@ -2109,35 +2191,35 @@ def generate_html(
 
         .match-score-label {{
             font-size: 11px;
-            color: #6b7280;
+            color: #4c1d95;
             text-transform: uppercase;
         }}
 
         .match-score-value {{
             font-size: 20px;
             font-weight: 700;
-            color: #6366f1;
+            color: #5b21b6;
         }}
 
         .match-title {{
-            color: #111827;
+            color: #3730a3;
             font-size: 16px;
             font-weight: 700;
         }}
 
         .match-category {{
-            color: #111827;
+            color: #3730a3;
             font-size: 16px;
             font-weight: 700;
         }}
 
         .match-hierarchy {{
-            color: #6b7280;
+            color: #4c1d95;
             font-size: 13px;
         }}
 
         .match-toggle-main {{
-            color: #9ca3af;
+            color: #5b21b6;
             font-size: 20px;
             transition: transform 0.3s;
             flex-shrink: 0;
@@ -2162,10 +2244,100 @@ def generate_html(
             margin-bottom: 0;
         }}
 
+        .step-section-wrapper {{
+            margin: 20px 25px;
+            border: 2px solid #e5e7eb;
+            border-radius: 12px;
+            overflow: hidden;
+            background: white;
+        }}
+
+        .step-1-wrapper {{
+            border-color: #fbbf24;
+        }}
+
+        .step-2-wrapper {{
+            border-color: #34d399;
+        }}
+
+        .step-header {{
+            background: linear-gradient(135deg, #fbbf24 0%, #fcd34d 100%);
+            padding: 16px 25px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            cursor: pointer;
+            transition: all 0.3s;
+        }}
+
+        .step-header:hover {{
+            background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
+        }}
+
+        .step-2-wrapper .step-header {{
+            background: linear-gradient(135deg, #34d399 0%, #6ee7b7 100%);
+        }}
+
+        .step-2-wrapper .step-header:hover {{
+            background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+        }}
+
+        .step-header-content {{
+            display: flex;
+            align-items: center;
+            gap: 12px;
+        }}
+
+        .step-header .step-toggle {{
+            font-size: 20px;
+            color: #78350f;
+            transition: transform 0.3s;
+        }}
+
+        .step-2-wrapper .step-header .step-toggle {{
+            color: #065f46;
+        }}
+
+        .step-section-wrapper.expanded > .step-header .step-toggle {{
+            transform: rotate(180deg);
+        }}
+
+        .step-wrapper-content {{
+            max-height: 0;
+            overflow: hidden;
+            transition: max-height 0.3s ease;
+        }}
+
+        .step-section-wrapper.expanded .step-wrapper-content {{
+            max-height: 10000px;
+        }}
+
+        .step-number-badge {{
+            background: rgba(255, 255, 255, 0.5);
+            color: #78350f;
+            padding: 6px 14px;
+            border-radius: 20px;
+            font-size: 13px;
+            font-weight: 700;
+            letter-spacing: 0.5px;
+        }}
+
+        .step-2-wrapper .step-number-badge {{
+            color: #065f46;
+        }}
+
+        .step-title {{
+            color: #78350f;
+            font-size: 16px;
+            font-weight: 700;
+        }}
+
+        .step-2-wrapper .step-title {{
+            color: #065f46;
+        }}
+
         .match-analysis-section {{
             padding: 25px;
-            border-top: 2px solid #e5e7eb;
-            margin-top: 15px;
         }}
 
         .match-header {{
@@ -2199,8 +2371,8 @@ def generate_html(
             border-radius: 4px;
             font-size: 11px;
             font-weight: 600;
-            background: #f3f4f6;
-            color: #6b7280;
+            background: rgba(91, 33, 182, 0.15);
+            color: #5b21b6;
         }}
 
         .match-element-name {{
@@ -2264,13 +2436,13 @@ def generate_html(
         }}
 
         .step-section-header {{
-            padding: 15px 20px;
+            padding: 18px 25px;
             background: #f9fafb;
             cursor: pointer;
             display: flex;
             justify-content: space-between;
             align-items: center;
-            border-bottom: 2px solid #e5e7eb;
+            border-bottom: 1px solid #e5e7eb;
         }}
 
         .step-section-header:hover {{
@@ -2279,11 +2451,24 @@ def generate_html(
 
         .step-section-title {{
             font-size: 15px;
-            font-weight: 700;
-            color: #1f2937;
+            font-weight: 600;
+            color: #374151;
             display: flex;
             align-items: center;
-            gap: 8px;
+            gap: 10px;
+        }}
+
+        .step-sub-number {{
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 38px;
+            padding: 4px 10px;
+            background: #10b981;
+            color: white;
+            border-radius: 6px;
+            font-size: 12px;
+            font-weight: 700;
         }}
 
         .step-number {{
@@ -2415,7 +2600,76 @@ def generate_html(
         }}
 
         .search-results-section {{
+            padding: 25px;
+        }}
 
+        .search-params-section {{
+            background: #f0f9ff;
+            border: 1px solid #bae6fd;
+            border-radius: 8px;
+            padding: 20px;
+            margin-bottom: 20px;
+        }}
+
+        .search-params-title {{
+            font-size: 15px;
+            font-weight: 700;
+            color: #0c4a6e;
+            margin-bottom: 16px;
+        }}
+
+        .search-params-grid {{
+            display: grid;
+            grid-template-columns: repeat(2, 1fr);
+            gap: 12px;
+        }}
+
+        .search-param-item {{
+            display: flex;
+            align-items: center;
+            gap: 8px;
+        }}
+
+        .search-param-label {{
+            font-size: 13px;
+            color: #0369a1;
+            font-weight: 600;
+            min-width: 80px;
+        }}
+
+        .search-param-value {{
+            font-size: 13px;
+            color: #0c4a6e;
+            background: #e0f2fe;
+            padding: 4px 12px;
+            border-radius: 4px;
+        }}
+
+        .search-summary-section {{
+            background: #ecfdf5;
+            border: 1px solid #a7f3d0;
+            border-radius: 8px;
+            padding: 16px 20px;
+            margin-bottom: 20px;
+        }}
+
+        .search-summary-title {{
+            font-size: 15px;
+            font-weight: 700;
+            color: #065f46;
+            margin-bottom: 8px;
+        }}
+
+        .search-summary-content {{
+            font-size: 14px;
+            color: #047857;
+        }}
+
+        .search-result-count {{
+            font-size: 18px;
+            font-weight: 700;
+            color: #059669;
+            margin: 0 4px;
         }}
 
         .search-notes-list {{