generate_html.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 生成单文件HTML可视化页面
  5. """
  6. import json
  7. import os
  8. def generate_single_html():
  9. """生成包含所有资源的单文件HTML"""
  10. # 读取各个文件
  11. with open('index.html', 'r', encoding='utf-8') as f:
  12. html_content = f.read()
  13. with open('css/style.css', 'r', encoding='utf-8') as f:
  14. css_content = f.read()
  15. with open('js/carousel.js', 'r', encoding='utf-8') as f:
  16. carousel_js = f.read()
  17. with open('js/card.js', 'r', encoding='utf-8') as f:
  18. card_js = f.read()
  19. with open('js/tree.js', 'r', encoding='utf-8') as f:
  20. tree_js = f.read()
  21. with open('js/main.js', 'r', encoding='utf-8') as f:
  22. main_js = f.read()
  23. with open('data/data.json', 'r', encoding='utf-8') as f:
  24. data = json.load(f)
  25. # 修改main.js中的数据加载逻辑
  26. main_js_modified = main_js.replace(
  27. 'const response = await fetch(\'data/data.json\');',
  28. '// 使用内联数据'
  29. ).replace(
  30. '''if (!response.ok) {
  31. throw new Error(`HTTP error! status: ${response.status}`);
  32. }
  33. this.data = await response.json();''',
  34. 'this.data = window.__INLINE_DATA__;'
  35. )
  36. # 构建完整的HTML(使用与index.html完全一致的结构)
  37. single_html = f'''<!DOCTYPE html>
  38. <html lang="zh-CN">
  39. <head>
  40. <meta charset="UTF-8">
  41. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  42. <title>小红书搜索结果可视化</title>
  43. <style>
  44. {css_content}
  45. </style>
  46. </head>
  47. <body>
  48. <!-- 顶部标题栏 -->
  49. <header class="header">
  50. <div class="header-content">
  51. <h1 class="header-title">🔍 小红书搜索结果可视化</h1>
  52. <div class="header-stats">
  53. <span class="stat-item">
  54. <span class="stat-label">结果项:</span>
  55. <span class="stat-value" id="stat-results">0</span>
  56. </span>
  57. <span class="stat-item">
  58. <span class="stat-label">特征数:</span>
  59. <span class="stat-value" id="stat-features">0</span>
  60. </span>
  61. <span class="stat-item">
  62. <span class="stat-label">帖子数:</span>
  63. <span class="stat-value" id="stat-notes">0</span>
  64. </span>
  65. </div>
  66. </div>
  67. <!-- 搜索和筛选栏 -->
  68. <div class="toolbar">
  69. <div class="search-box">
  70. <input type="text"
  71. id="search-input"
  72. class="search-input"
  73. placeholder="搜索特征名称或关键词...">
  74. <button class="search-btn" id="search-btn">🔍</button>
  75. </div>
  76. <div class="filter-box">
  77. <select id="filter-status" class="filter-select">
  78. <option value="all">全部状态</option>
  79. <option value="success">搜索成功</option>
  80. <option value="failed">搜索失败</option>
  81. <option value="pending">待搜索</option>
  82. </select>
  83. <select id="sort-by" class="filter-select">
  84. <option value="default">默认排序</option>
  85. <option value="note_count">按帖子数</option>
  86. <option value="weight">按权重</option>
  87. </select>
  88. <button class="theme-toggle" id="theme-toggle" title="切换主题">🌙</button>
  89. </div>
  90. </div>
  91. </header>
  92. <!-- 主内容区域 -->
  93. <main class="main-container">
  94. <!-- 左侧树形列表 -->
  95. <aside class="sidebar">
  96. <div class="sidebar-header">
  97. <h2 class="sidebar-title">特征树形结构</h2>
  98. <button class="collapse-all-btn" id="collapse-all">全部折叠</button>
  99. </div>
  100. <div class="tree-container" id="tree-container">
  101. <div class="loading">加载中...</div>
  102. </div>
  103. </aside>
  104. <!-- 右侧详情面板 -->
  105. <section class="content-panel">
  106. <div class="panel-header">
  107. <h2 class="panel-title" id="panel-title">搜索结果详情</h2>
  108. <div class="panel-info" id="panel-info"></div>
  109. </div>
  110. <div class="cards-container" id="cards-container">
  111. <div class="empty-state">
  112. <div class="empty-icon">📋</div>
  113. <p class="empty-text">请从左侧选择一个特征查看搜索结果</p>
  114. </div>
  115. </div>
  116. </section>
  117. </main>
  118. <!-- 图片查看器模态框 -->
  119. <div class="modal" id="image-modal">
  120. <div class="modal-overlay" id="modal-overlay"></div>
  121. <div class="modal-content">
  122. <button class="modal-close" id="modal-close">×</button>
  123. <button class="modal-prev" id="modal-prev">‹</button>
  124. <button class="modal-next" id="modal-next">›</button>
  125. <img class="modal-image" id="modal-image" src="" alt="">
  126. <div class="modal-caption" id="modal-caption"></div>
  127. </div>
  128. </div>
  129. <script>
  130. // 内联数据
  131. window.__INLINE_DATA__ = {json.dumps(data, ensure_ascii=False, indent=2)};
  132. // Carousel组件
  133. {carousel_js}
  134. // Card组件
  135. {card_js}
  136. // Tree组件
  137. {tree_js}
  138. // Main应用
  139. {main_js_modified}
  140. </script>
  141. </body>
  142. </html>'''
  143. # 写入文件 - 使用新文件名不覆盖原有文件
  144. import datetime
  145. timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
  146. output_file = f'xiaohongshu_visualization_{timestamp}.html'
  147. with open(output_file, 'w', encoding='utf-8') as f:
  148. f.write(single_html)
  149. # 获取文件大小
  150. file_size = os.path.getsize(output_file) / (1024 * 1024)
  151. print(f"✅ 已生成单文件HTML: {output_file}")
  152. print(f"📦 文件大小: {file_size:.1f} MB")
  153. print(f"🚀 可以直接拖拽到浏览器打开")
  154. return output_file
  155. if __name__ == '__main__':
  156. generate_single_html()