report_template.html 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1.0">
  6. <title>{{industryName}}行业竞品与B端线索分析报告</title>
  7. <!-- ===== Chart.js CDN ===== -->
  8. <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
  9. <style>
  10. /* ============================================================
  11. 全局变量 & 色彩体系 — 轻奢深棕 + 暖金莫兰迪
  12. ============================================================ */
  13. :root{
  14. --bg-deep:#1A110B;
  15. --bg-dark:#2D1810;
  16. --bg-mid:#3E2518;
  17. --brown:#5C3D2E;
  18. --brown-light:#8B6F47;
  19. --gold:#C5A55A;
  20. --gold-light:#D4B978;
  21. --cream:#FFF8F0;
  22. --cream-dim:#F5E6D3;
  23. --accent-rose:#C4868B;
  24. --accent-sage:#8FA68A;
  25. --accent-blue:#7E9BB5;
  26. --glass:rgba(255,248,240,0.06);
  27. --glass-border:rgba(197,165,90,0.18);
  28. --glass-hover:rgba(255,248,240,0.12);
  29. --shadow:0 8px 32px rgba(0,0,0,0.35);
  30. --radius:16px;
  31. --transition:all .4s cubic-bezier(.4,0,.2,1);
  32. --font:'Segoe UI','PingFang SC','Microsoft YaHei','Helvetica Neue',system-ui,sans-serif;
  33. }
  34. /* ============================================================
  35. Reset & 基础排版
  36. ============================================================ */
  37. *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
  38. html{font-size:16px;scroll-behavior:smooth;overflow:hidden}
  39. body{font-family:var(--font);background:var(--bg-deep);color:var(--cream);line-height:1.7;overflow:hidden;-webkit-font-smoothing:antialiased}
  40. h1{font-size:clamp(2rem,5vw,3.6rem);font-weight:800;letter-spacing:-.02em;line-height:1.15}
  41. h2{font-size:clamp(1.5rem,3.5vw,2.4rem);font-weight:700;letter-spacing:-.01em}
  42. h3{font-size:clamp(1.1rem,2vw,1.5rem);font-weight:600}
  43. a{color:var(--gold);text-decoration:none}
  44. /* ============================================================
  45. 幻灯片容器
  46. ============================================================ */
  47. #slides-wrapper{position:relative;width:100vw;height:100vh;overflow:hidden}
  48. .slide{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:clamp(24px,4vw,60px);opacity:0;visibility:hidden;transform:translateY(30px);transition:opacity .7s ease,transform .7s ease,visibility 0s .7s;z-index:0;overflow-y:auto}
  49. .slide.active{opacity:1;visibility:visible;transform:translateY(0);transition:opacity .7s ease,transform .7s ease,visibility 0s 0s;z-index:1}
  50. .slide-inner{width:100%;max-width:1200px;position:relative}
  51. /* ============================================================
  52. 页眉 & 页脚
  53. ============================================================ */
  54. .slide-header{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:18px clamp(24px,4vw,60px);font-size:.8rem;color:var(--brown-light);letter-spacing:.05em;z-index:10;pointer-events:none}
  55. .slide-footer{position:absolute;bottom:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:14px clamp(24px,4vw,60px);font-size:.75rem;color:rgba(139,111,71,.6);z-index:10;pointer-events:none}
  56. .page-num{font-weight:600;color:var(--gold)}
  57. /* ============================================================
  58. 毛玻璃卡片(Glassmorphism)
  59. ============================================================ */
  60. .glass-card{background:var(--glass);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--glass-border);border-radius:var(--radius);padding:clamp(18px,2.5vw,32px);transition:var(--transition);position:relative;overflow:hidden}
  61. .glass-card::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(197,165,90,.08) 0%,transparent 60%);pointer-events:none;border-radius:inherit}
  62. .glass-card:hover{background:var(--glass-hover);border-color:rgba(197,165,90,.35);transform:translateY(-2px);box-shadow:var(--shadow)}
  63. /* ============================================================
  64. 数据指标卡片
  65. ============================================================ */
  66. .metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:clamp(12px,2vw,24px);width:100%}
  67. .metric-card{text-align:center;padding:clamp(16px,2vw,28px)}
  68. .metric-value{font-size:clamp(1.8rem,4vw,2.8rem);font-weight:800;color:var(--gold);margin-bottom:4px}
  69. .metric-label{font-size:.85rem;color:var(--cream-dim);font-weight:400}
  70. .metric-sub{font-size:.75rem;color:var(--brown-light);margin-top:2px}
  71. /* ============================================================
  72. 数据表格
  73. ============================================================ */
  74. .data-table{width:100%;border-collapse:separate;border-spacing:0;font-size:clamp(.75rem,1.4vw,.9rem)}
  75. .data-table thead th{background:rgba(197,165,90,.15);color:var(--gold-light);font-weight:600;padding:12px 14px;text-align:left;border-bottom:2px solid var(--gold);white-space:nowrap}
  76. .data-table thead th:first-child{border-radius:var(--radius) 0 0 0}
  77. .data-table thead th:last-child{border-radius:0 var(--radius) 0 0}
  78. .data-table tbody td{padding:10px 14px;border-bottom:1px solid rgba(139,111,71,.15);transition:background .3s}
  79. .data-table tbody tr:hover td{background:rgba(197,165,90,.06)}
  80. .data-table tbody tr:last-child td:first-child{border-radius:0 0 0 var(--radius)}
  81. .data-table tbody tr:last-child td:last-child{border-radius:0 0 var(--radius) 0}
  82. /* ============================================================
  83. 标签 / 徽章
  84. ============================================================ */
  85. .badge{display:inline-block;padding:3px 10px;border-radius:20px;font-size:.72rem;font-weight:600;letter-spacing:.03em}
  86. .badge-gold{background:rgba(197,165,90,.2);color:var(--gold-light);border:1px solid rgba(197,165,90,.3)}
  87. .badge-rose{background:rgba(196,134,139,.15);color:var(--accent-rose);border:1px solid rgba(196,134,139,.25)}
  88. .badge-sage{background:rgba(143,166,138,.15);color:var(--accent-sage);border:1px solid rgba(143,166,138,.25)}
  89. .badge-blue{background:rgba(126,155,181,.15);color:var(--accent-blue);border:1px solid rgba(126,155,181,.25)}
  90. /* ============================================================
  91. 进度条 / 比例条
  92. ============================================================ */
  93. .bar-row{display:flex;align-items:center;gap:12px;margin-bottom:10px}
  94. .bar-label{min-width:140px;font-size:.82rem;text-align:right;color:var(--cream-dim)}
  95. .bar-track{flex:1;height:22px;background:rgba(255,248,240,.06);border-radius:11px;overflow:hidden;position:relative}
  96. .bar-fill{height:100%;border-radius:11px;transition:width 1.5s cubic-bezier(.4,0,.2,1);position:relative}
  97. .bar-fill::after{content:attr(data-value);position:absolute;right:8px;top:50%;transform:translateY(-50%);font-size:.7rem;font-weight:700;color:var(--bg-dark)}
  98. /* ============================================================
  99. 侧边导航
  100. ============================================================ */
  101. #side-nav{position:fixed;right:clamp(10px,2vw,24px);top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:12px;z-index:1000}
  102. .nav-dot{width:12px;height:12px;border-radius:50%;border:2px solid var(--brown-light);background:transparent;cursor:pointer;transition:var(--transition);position:relative}
  103. .nav-dot.active{background:var(--gold);border-color:var(--gold);box-shadow:0 0 12px rgba(197,165,90,.5)}
  104. .nav-dot::after{content:attr(data-tip);position:absolute;right:22px;top:50%;transform:translateY(-50%);background:var(--bg-mid);color:var(--cream);padding:4px 12px;border-radius:8px;font-size:.72rem;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .3s}
  105. .nav-dot:hover::after{opacity:1}
  106. /* ============================================================
  107. 全屏按钮
  108. ============================================================ */
  109. #fs-btn{position:fixed;top:18px;right:clamp(10px,2vw,24px);z-index:1001;background:var(--glass);backdrop-filter:blur(10px);border:1px solid var(--glass-border);color:var(--gold);padding:6px 14px;border-radius:8px;cursor:pointer;font-size:.78rem;transition:var(--transition)}
  110. #fs-btn:hover{background:var(--glass-hover);border-color:var(--gold)}
  111. /* ============================================================
  112. 封面粒子画布
  113. ============================================================ */
  114. #particles-canvas{position:absolute;inset:0;z-index:0;pointer-events:none}
  115. /* ============================================================
  116. 图表容器
  117. ============================================================ */
  118. .chart-wrap{position:relative;width:100%;max-height:320px;margin:12px 0}
  119. .chart-wrap canvas{max-height:320px}
  120. /* ============================================================
  121. 两栏/三栏布局
  122. ============================================================ */
  123. .cols-2{display:grid;grid-template-columns:1fr 1fr;gap:clamp(14px,2vw,28px)}
  124. .cols-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:clamp(14px,2vw,24px)}
  125. /* ============================================================
  126. 分隔线 & 间距工具
  127. ============================================================ */
  128. .divider{width:60px;height:3px;background:linear-gradient(90deg,var(--gold),transparent);border-radius:2px;margin:16px 0}
  129. .mt-sm{margin-top:12px}.mt-md{margin-top:24px}.mt-lg{margin-top:36px}
  130. .mb-sm{margin-bottom:12px}.mb-md{margin-bottom:24px}
  131. .text-gold{color:var(--gold)}.text-rose{color:var(--accent-rose)}.text-sage{color:var(--accent-sage)}.text-blue{color:var(--accent-blue)}
  132. .text-dim{color:var(--brown-light)}.text-sm{font-size:.82rem}.text-xs{font-size:.72rem}
  133. .fw-700{font-weight:700}.fw-600{font-weight:600}
  134. /* ============================================================
  135. 封面页特殊样式
  136. ============================================================ */
  137. .cover-content{position:relative;z-index:1;text-align:center}
  138. .cover-brand{font-size:clamp(.9rem,1.8vw,1.2rem);color:var(--gold);letter-spacing:.3em;text-transform:uppercase;margin-bottom:16px;font-weight:600}
  139. .cover-title{background:linear-gradient(135deg,var(--cream) 0%,var(--gold-light) 50%,var(--gold) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:20px}
  140. .cover-sub{font-size:clamp(.9rem,1.6vw,1.15rem);color:var(--cream-dim);max-width:640px;margin:0 auto 32px}
  141. .cover-date{font-size:.85rem;color:var(--brown-light);letter-spacing:.1em}
  142. .cover-line{width:80px;height:1px;background:var(--gold);margin:24px auto}
  143. /* ============================================================
  144. 目录页
  145. ============================================================ */
  146. .toc-list{list-style:none;counter-reset:toc}
  147. .toc-item{counter-increment:toc;padding:14px 20px;border-bottom:1px solid rgba(139,111,71,.12);cursor:pointer;display:flex;align-items:center;gap:16px;transition:var(--transition);border-radius:8px}
  148. .toc-item:hover{background:var(--glass-hover);padding-left:28px}
  149. .toc-item::before{content:counter(toc,decimal-leading-zero);font-size:1.6rem;font-weight:800;color:var(--gold);min-width:40px}
  150. .toc-item .toc-title{font-size:1.05rem;font-weight:600;color:var(--cream)}
  151. .toc-item .toc-desc{font-size:.78rem;color:var(--brown-light);margin-top:2px}
  152. /* ============================================================
  153. 竞品卡片
  154. ============================================================ */
  155. .competitor-card{padding:clamp(18px,2vw,28px)}
  156. .competitor-card h3{color:var(--gold-light);margin-bottom:8px}
  157. .comp-stat{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid rgba(139,111,71,.1);font-size:.85rem}
  158. /* ============================================================
  159. 时间轴
  160. ============================================================ */
  161. .timeline{position:relative;padding-left:28px}
  162. .timeline::before{content:'';position:absolute;left:8px;top:0;bottom:0;width:2px;background:linear-gradient(180deg,var(--gold),var(--brown-light),transparent)}
  163. .timeline-item{position:relative;margin-bottom:24px;padding-left:20px}
  164. .timeline-item::before{content:'';position:absolute;left:-24px;top:6px;width:12px;height:12px;border-radius:50%;background:var(--gold);border:2px solid var(--bg-dark);box-shadow:0 0 8px rgba(197,165,90,.4)}
  165. .timeline-item h4{color:var(--gold-light);font-size:.95rem;margin-bottom:4px}
  166. .timeline-item p{font-size:.82rem;color:var(--cream-dim)}
  167. /* ============================================================
  168. VOC 痛点条
  169. ============================================================ */
  170. .pain-bar{display:flex;align-items:center;gap:12px;margin-bottom:14px}
  171. .pain-icon{font-size:1.1rem;min-width:24px;text-align:center}
  172. .pain-info{flex:1}
  173. .pain-name{font-size:.85rem;font-weight:600;color:var(--cream);margin-bottom:3px}
  174. .pain-pct{font-size:.75rem;color:var(--brown-light)}
  175. .pain-track{width:100%;height:8px;background:rgba(255,248,240,.06);border-radius:4px;overflow:hidden}
  176. .pain-fill{height:100%;border-radius:4px;transition:width 1.5s ease}
  177. /* ============================================================
  178. 动画关键帧
  179. ============================================================ */
  180. @keyframes fadeInUp{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}
  181. @keyframes fadeIn{from{opacity:0}to{opacity:1}}
  182. @keyframes shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}
  183. @keyframes pulse{0%,100%{opacity:1}50%{opacity:.6}}
  184. .anim-up{animation:fadeInUp .8s ease both}
  185. .anim-delay-1{animation-delay:.15s}.anim-delay-2{animation-delay:.3s}.anim-delay-3{animation-delay:.45s}.anim-delay-4{animation-delay:.6s}
  186. /* 入场动画 */
  187. .anim-enter{opacity:0;transform:translateY(24px);transition:opacity .7s ease,transform .7s ease}
  188. .slide.active .anim-enter{opacity:1;transform:translateY(0)}
  189. .slide.active .anim-enter.d1{transition-delay:.1s}
  190. .slide.active .anim-enter.d2{transition-delay:.2s}
  191. .slide.active .anim-enter.d3{transition-delay:.3s}
  192. .slide.active .anim-enter.d4{transition-delay:.4s}
  193. .slide.active .anim-enter.d5{transition-delay:.5s}
  194. .slide.active .anim-enter.d6{transition-delay:.6s}
  195. /* ============================================================
  196. Modal弹窗
  197. ============================================================ */
  198. .modal-overlay{position:fixed;inset:0;background:rgba(10,6,3,.85);backdrop-filter:blur(12px);z-index:2000;display:none;align-items:center;justify-content:center;opacity:0;transition:opacity .4s ease}
  199. .modal-overlay.show{display:flex;opacity:1}
  200. .modal-box{background:linear-gradient(135deg,#1a110b,#0d0a08);border:1px solid rgba(197,165,90,.25);border-radius:16px;width:92vw;max-width:960px;max-height:88vh;overflow-y:auto;box-shadow:0 8px 48px rgba(0,0,0,.7);position:relative;animation:modalIn .4s ease}
  201. @keyframes modalIn{from{opacity:0;transform:translateY(30px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}
  202. .modal-close{position:absolute;top:12px;right:16px;background:none;border:none;color:var(--cream-dim);font-size:1.4rem;cursor:pointer;z-index:10;transition:color .3s}
  203. .modal-close:hover{color:var(--gold)}
  204. .modal-header{padding:24px 28px 16px;border-bottom:1px solid rgba(197,165,90,.15)}
  205. .modal-header h2{font-size:1.15rem;color:var(--gold);margin:0 0 6px}
  206. .modal-header p{font-size:.8rem;color:var(--cream-dim);margin:0}
  207. .modal-body{padding:20px 28px 28px}
  208. /* ============================================================
  209. 响应式
  210. ============================================================ */
  211. @media(max-width:900px){
  212. .cols-2,.cols-3{grid-template-columns:1fr}
  213. .metric-grid{grid-template-columns:repeat(2,1fr)}
  214. #side-nav{right:6px;gap:8px}
  215. .nav-dot{width:9px;height:9px}
  216. .bar-label{min-width:100px;font-size:.75rem}
  217. }
  218. @media(max-width:600px){
  219. .metric-grid{grid-template-columns:1fr}
  220. .slide{padding:16px}
  221. }
  222. </style>
  223. </head>
  224. <body>
  225. <!-- ===== 全屏按钮 ===== -->
  226. <button id="fs-btn" title="全屏切换">⛶ 全屏</button>
  227. <!-- ===== 侧边导航(由 JS 自动生成) ===== -->
  228. <nav id="side-nav"></nav>
  229. <!-- ===== 幻灯片容器 ===== -->
  230. <div id="slides-wrapper">
  231. <!-- ════════════════════════════════════════════
  232. SLIDE 0 — 封面
  233. ════════════════════════════════════════════ -->
  234. <section class="slide active" id="slide-0">
  235. <canvas id="particles-canvas"></canvas>
  236. <div class="cover-content">
  237. <p class="cover-brand">{{coverBrandEnglish}}</p>
  238. <h1 class="cover-title">{{industryName}}行业竞品与<br>B端线索分析报告</h1>
  239. <div class="cover-line"></div>
  240. <p class="cover-sub">基于Amazon电商数据 · TikTok/Instagram社媒数据 · {{totalReviews}}条用户评论VOC分析<br>覆盖{{categoryCount}}大核心品类 · {{competitorCount}}大竞品品牌深度对标</p>
  241. <p class="cover-date">{{reportDate}} · AI Data Intelligence</p>
  242. </div>
  243. <div class="slide-footer"><span></span><span class="text-xs" style="opacity:.4">按 → 或滚轮翻页</span></div>
  244. </section>
  245. <!-- ════════════════════════════════════════════
  246. SLIDE 1 — 目录
  247. ════════════════════════════════════════════ -->
  248. <section class="slide" id="slide-1">
  249. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>目录</span></div>
  250. <div class="slide-inner" style="max-width:720px">
  251. <h2 class="text-gold mb-md" style="text-align:center">报告目录</h2>
  252. <div class="divider" style="margin:0 auto 28px"></div>
  253. <ul class="toc-list">
  254. <li class="toc-item" onclick="goSlide(2)"><div><div class="toc-title">核心执行摘要</div><div class="toc-desc">关键数据 · 核心结论 · 核心价值</div></div></li>
  255. <li class="toc-item" onclick="goSlide(3)"><div><div class="toc-title">{{industryName}}品类市场全景分析</div><div class="toc-desc">{{categoryCount}}大品类规模 · 增长趋势 · 价格带 · 品牌分布</div></div></li>
  256. <li class="toc-item" onclick="goSlide(4)"><div><div class="toc-title">核心竞品深度对标分析</div><div class="toc-desc">{{competitorNames}} 品牌拆解</div></div></li>
  257. <li class="toc-item" onclick="goSlide(5)"><div><div class="toc-title">全渠道用户VOC与需求洞察</div><div class="toc-desc">{{totalReviews}}条评论 · 痛点 · 喜爱点 · 使用场景</div></div></li>
  258. <li class="toc-item" onclick="goSlide(6)"><div><div class="toc-title">B端高价值线索与品类矩阵</div><div class="toc-desc">品类机会评估 · 社媒热度 · 优先级排序</div></div></li>
  259. <li class="toc-item" onclick="goSlide(7)"><div><div class="toc-title">市场拓展与落地执行建议</div><div class="toc-desc">短期·中期·长期分阶段策略</div></div></li>
  260. <li class="toc-item" onclick="goSlide(8)"><div><div class="toc-title">总结与展望</div><div class="toc-desc">核心机会点 · 行动优先级</div></div></li>
  261. </ul>
  262. </div>
  263. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">01</span></div>
  264. </section>
  265. <!-- ════════════════════════════════════════════
  266. SLIDE 2 — 执行摘要
  267. ════════════════════════════════════════════ -->
  268. <section class="slide" id="slide-2">
  269. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>执行摘要</span></div>
  270. <div class="slide-inner">
  271. <h2 class="text-gold mb-sm">核心执行摘要</h2>
  272. <div class="divider"></div>
  273. <!-- 核心数据指标卡片 — 由 REPORT_DATA.metrics 注入 -->
  274. <div class="metric-grid mt-md mb-md" id="exec-metrics"></div>
  275. <!-- 核心结论 — 由 REPORT_DATA.coreFindings / coreValues 注入 -->
  276. <div class="cols-2">
  277. <div class="glass-card">
  278. <h3 class="text-gold mb-sm">📊 核心发现</h3>
  279. <ul id="core-findings" style="list-style:none;font-size:.85rem;color:var(--cream-dim)"></ul>
  280. </div>
  281. <div class="glass-card">
  282. <h3 class="text-gold mb-sm">💡 核心价值</h3>
  283. <ul id="core-values" style="list-style:none;font-size:.85rem;color:var(--cream-dim)"></ul>
  284. </div>
  285. </div>
  286. </div>
  287. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">02</span></div>
  288. </section>
  289. <!-- ════════════════════════════════════════════
  290. SLIDE 3 — 市场全景
  291. ════════════════════════════════════════════ -->
  292. <section class="slide" id="slide-3">
  293. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>市场全景</span></div>
  294. <div class="slide-inner">
  295. <h2 class="text-gold mb-sm">{{industryName}}品类市场全景分析</h2>
  296. <div class="divider mb-md"></div>
  297. <div class="cols-2">
  298. <div class="glass-card">
  299. <h3 class="text-sm fw-600 mb-sm">Top100 周均销售额(美元)</h3>
  300. <div class="chart-wrap"><canvas id="chart-revenue"></canvas></div>
  301. </div>
  302. <div class="glass-card">
  303. <h3 class="text-sm fw-600 mb-sm">品类增长率(同比)</h3>
  304. <div class="chart-wrap"><canvas id="chart-growth"></canvas></div>
  305. </div>
  306. </div>
  307. <!-- 数据表格 — 由 REPORT_DATA.categoryTable 注入 -->
  308. <div class="glass-card mt-md" style="overflow-x:auto">
  309. <table class="data-table">
  310. <thead><tr>
  311. <th>品类</th><th>周均销量</th><th>周均销售额</th><th>均价</th><th>品牌数</th><th>增长率</th><th>中国卖家</th>
  312. </tr></thead>
  313. <tbody id="market-table-body"></tbody>
  314. </table>
  315. </div>
  316. </div>
  317. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">03</span></div>
  318. </section>
  319. <!-- ════════════════════════════════════════════
  320. SLIDE 4 — 竞品对标
  321. ════════════════════════════════════════════ -->
  322. <section class="slide" id="slide-4">
  323. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>竞品对标</span></div>
  324. <div class="slide-inner">
  325. <h2 class="text-gold mb-sm">核心竞品深度对标分析</h2>
  326. <div class="divider mb-md"></div>
  327. <!-- 竞品卡片 — 由 REPORT_DATA.competitors 注入 -->
  328. <div class="cols-3" id="competitor-cards"></div>
  329. <!-- 雷达图 -->
  330. <div class="glass-card mt-md" style="max-width:480px;margin-left:auto;margin-right:auto">
  331. <h3 class="text-sm fw-600 mb-sm" style="text-align:center">竞品综合能力雷达图</h3>
  332. <div class="chart-wrap" style="max-height:260px"><canvas id="chart-radar"></canvas></div>
  333. </div>
  334. </div>
  335. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">04</span></div>
  336. </section>
  337. <!-- ════════════════════════════════════════════
  338. SLIDE 5 — VOC 用户洞察
  339. ════════════════════════════════════════════ -->
  340. <section class="slide" id="slide-5">
  341. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>用户VOC</span></div>
  342. <div class="slide-inner">
  343. <h2 class="text-gold mb-sm">全渠道用户VOC与需求洞察</h2>
  344. <p class="text-dim text-sm mb-md">基于品类 Top 畅销产品 <strong class="text-gold" id="voc-review-count">0</strong> 条真实用户评论深度分析</p>
  345. <div class="cols-2">
  346. <!-- 左:痛点分析 -->
  347. <div class="glass-card">
  348. <h3 class="text-rose text-sm fw-600 mb-sm">🔴 用户核心痛点(差评Top5)</h3>
  349. <div id="pain-points-container"></div>
  350. </div>
  351. <!-- 右:喜爱点 + 场景 -->
  352. <div>
  353. <div class="glass-card mb-sm">
  354. <h3 class="text-sage text-sm fw-600 mb-sm">🟢 用户最爱亮点(好评Top5)</h3>
  355. <div id="highlights-container"></div>
  356. </div>
  357. <div class="glass-card">
  358. <h3 class="text-sm fw-600 mb-sm">📍 使用场景分布</h3>
  359. <div class="chart-wrap" style="max-height:180px"><canvas id="chart-scenes"></canvas></div>
  360. </div>
  361. </div>
  362. </div>
  363. </div>
  364. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">05</span></div>
  365. </section>
  366. <!-- ════════════════════════════════════════════
  367. SLIDE 6 — B端线索 & 品类矩阵
  368. ════════════════════════════════════════════ -->
  369. <section class="slide" id="slide-6">
  370. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>线索清单</span></div>
  371. <div class="slide-inner">
  372. <h2 class="text-gold mb-sm">B端高价值线索与品类机会矩阵</h2>
  373. <div class="divider mb-md"></div>
  374. <!-- 品类机会矩阵 — 由 REPORT_DATA.categoryMatrix 注入 -->
  375. <div class="glass-card mb-md" style="overflow-x:auto">
  376. <h3 class="text-sm fw-600 mb-sm">品类综合机会评估</h3>
  377. <table class="data-table">
  378. <thead><tr><th>品类</th><th>市场规模</th><th>增长性</th><th>社媒热度</th><th>竞争强度</th><th>中国卖家</th><th>综合推荐</th></tr></thead>
  379. <tbody id="matrix-table-body"></tbody>
  380. </table>
  381. </div>
  382. <div class="cols-2">
  383. <div class="glass-card">
  384. <h3 class="text-sm fw-600 mb-sm">TikTok 社媒热度排名</h3>
  385. <div class="chart-wrap"><canvas id="chart-tiktok"></canvas></div>
  386. </div>
  387. <div class="glass-card">
  388. <h3 class="text-sm fw-600 mb-sm">季节性与要点</h3>
  389. <div id="seasonality-notes" style="font-size:.85rem;color:var(--cream-dim)"></div>
  390. </div>
  391. </div>
  392. </div>
  393. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">06</span></div>
  394. </section>
  395. <!-- ════════════════════════════════════════════
  396. SLIDE 7 — 执行策略
  397. ════════════════════════════════════════════ -->
  398. <section class="slide" id="slide-7">
  399. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>执行策略</span></div>
  400. <div class="slide-inner">
  401. <h2 class="text-gold mb-sm">市场拓展与落地执行建议</h2>
  402. <div class="divider mb-md"></div>
  403. <!-- 短中长期策略 — 由 REPORT_DATA.strategy 注入 -->
  404. <div class="cols-3" id="strategy-columns"></div>
  405. <!-- VOC驱动的产品策略 -->
  406. <div class="glass-card mt-md">
  407. <h3 class="text-sm fw-600 mb-sm">🔬 VOC驱动的产品开发优先级</h3>
  408. <div id="voc-priorities" style="display:flex;flex-wrap:wrap;gap:12px;font-size:.82rem"></div>
  409. </div>
  410. </div>
  411. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">07</span></div>
  412. </section>
  413. <!-- ════════════════════════════════════════════
  414. SLIDE 8 — 总结与展望
  415. ════════════════════════════════════════════ -->
  416. <section class="slide" id="slide-8">
  417. <div class="slide-header"><span>{{industryName}}行业竞品分析报告</span><span>总结展望</span></div>
  418. <div class="slide-inner" style="max-width:900px">
  419. <h2 class="text-gold mb-sm" style="text-align:center">总结与展望</h2>
  420. <div class="divider" style="margin:0 auto 32px"></div>
  421. <!-- 由 REPORT_DATA.conclusion 注入 -->
  422. <div class="cols-2 mb-md" id="conclusion-top-cards"></div>
  423. <div class="glass-card mb-md" id="conclusion-differentiator" style="text-align:center;padding:28px"></div>
  424. <div class="glass-card" id="conclusion-actions" style="background:linear-gradient(135deg,rgba(197,165,90,.1),rgba(143,166,138,.08));border-color:rgba(197,165,90,.25)"></div>
  425. </div>
  426. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">08</span></div>
  427. </section>
  428. <!-- ════════════════════════════════════════════
  429. SLIDE 9 — 致谢
  430. ════════════════════════════════════════════ -->
  431. <section class="slide" id="slide-9">
  432. <div class="cover-content">
  433. <div class="cover-line" style="margin-bottom:32px"></div>
  434. <h2 style="font-size:clamp(1.6rem,3.5vw,2.6rem);background:linear-gradient(135deg,var(--cream),var(--gold-light));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:16px">感谢您的关注</h2>
  435. <p class="text-dim" style="font-size:1rem;max-width:520px;margin:0 auto 28px">期待与您携手,用AI-VOC系统共同开拓{{industryName}}市场新蓝海</p>
  436. <div class="cover-line"></div>
  437. <div style="margin-top:32px;font-size:.85rem;color:var(--brown-light)">
  438. <p>报告编制:AI-VOC 智能数据分析系统</p>
  439. <p>数据来源:Amazon Sorftime API · TikHub (TikTok & Instagram) · AI-VOC Engine</p>
  440. <p>数据时效:截至 {{reportDate}}</p>
  441. <p style="margin-top:16px;font-size:.82rem;color:var(--gold)">📧 联系我们,获取专属VOC系统试用</p>
  442. <p style="margin-top:12px;font-size:.75rem;opacity:.6">本报告数据来自第三方API,仅供商业决策参考</p>
  443. </div>
  444. </div>
  445. <div class="slide-footer"><span>{{industryName}}行业竞品分析报告</span><span class="page-num">09</span></div>
  446. </section>
  447. </div><!-- /slides-wrapper -->
  448. <!-- ===== Modal弹窗容器 ===== -->
  449. <div class="modal-overlay" id="modal-overlay">
  450. <div class="modal-box">
  451. <button class="modal-close" id="modal-close">&times;</button>
  452. <div class="modal-header" id="modal-header"></div>
  453. <div class="modal-body" id="modal-body"></div>
  454. </div>
  455. </div>
  456. <!-- ============================================================
  457. REPORT DATA — 由编排引擎注入
  458. ============================================================ -->
  459. <script>
  460. /**
  461. * REPORT_DATA: 所有报告数据由 workflow Stage 7 组装后注入到此对象。
  462. * 模板中所有 DOM 操作均基于此数据驱动渲染。
  463. *
  464. * 结构说明:
  465. * - meta: 报告元信息
  466. * - metrics: 执行摘要指标卡片 [{value, prefix, suffix, label, sub}]
  467. * - coreFindings: 核心发现列表 [{text, highlight, highlightClass}]
  468. * - coreValues: 核心价值列表 [string]
  469. * - categories: 品类市场数据 [{name, weeklySales, weeklyRevenue, avgPrice, brands, growth, chinaRatio, highlight, revenueNum, growthNum}]
  470. * - competitors: 竞品数据 [{name, positioning, positionBadge, priceRange, igFollowers, tiktokFollowers, skuNote, strategyNote, pros, cons, radarData:[5]}]
  471. * - vocAnalysis: VOC分析 {totalReviews, painPoints:[{icon, name, pct, gradient, quote}], highlights:[{name, pct, color}], scenes:[{name, pct}]}
  472. * - categoryMatrix: 品类机会矩阵 [{name, marketSize, growth, socialHeat, competition, chinaRatio, recommendation, badgeClass}]
  473. * - tiktokHeat: TikTok热度 [{name, plays}]
  474. * - seasonalNotes: 季节性要点 [{icon, title, desc}]
  475. * - strategy: 执行策略 {shortTerm:[{title, desc}], midTerm:[{title, desc}], longTerm:[{title, desc}]}
  476. * - vocPriorities: VOC驱动优先级 [{text, badgeClass}]
  477. * - conclusion: 总结 {topCategory:{emoji, title, name, desc}, socialDividend:{emoji, title, name, desc}, differentiator:{title, subtitle, desc}, keyActions:[{emoji, title, desc}]}
  478. * - trendData: 趋势数据 {weeks:[], datasets:[{label, data, borderColor}]}
  479. */
  480. const REPORT_DATA = /*{{REPORT_DATA_JSON}}*/ {};
  481. </script>
  482. <!-- ============================================================
  483. JavaScript — 导航 · 动画 · 数据驱动渲染 · 图表
  484. ============================================================ -->
  485. <script>
  486. /* ===== 全局状态 ===== */
  487. let currentSlide = 0;
  488. const totalSlides = document.querySelectorAll('.slide').length;
  489. let isTransitioning = false;
  490. /* ===== 自动生成侧边导航 ===== */
  491. (function buildNav(){
  492. const tips = ['封面','目录','执行摘要','市场全景','竞品对标','用户VOC','线索清单','执行策略','总结展望','致谢'];
  493. const nav = document.getElementById('side-nav');
  494. tips.forEach((tip,i) => {
  495. const d = document.createElement('div');
  496. d.className = 'nav-dot' + (i===0?' active':'');
  497. d.dataset.tip = tip;
  498. d.dataset.slide = i;
  499. d.addEventListener('click', () => goSlide(i));
  500. nav.appendChild(d);
  501. });
  502. })();
  503. const dots = document.querySelectorAll('.nav-dot');
  504. /* ===== 幻灯片切换 ===== */
  505. function goSlide(n) {
  506. if (isTransitioning || n === currentSlide || n < 0 || n >= totalSlides) return;
  507. isTransitioning = true;
  508. const slides = document.querySelectorAll('.slide');
  509. slides[currentSlide].classList.remove('active');
  510. slides[n].classList.add('active');
  511. dots.forEach((d, i) => d.classList.toggle('active', i === n));
  512. currentSlide = n;
  513. triggerSlideAnimations(n);
  514. setTimeout(() => { isTransitioning = false; }, 750);
  515. }
  516. function nextSlide() { goSlide(currentSlide + 1); }
  517. function prevSlide() { goSlide(currentSlide - 1); }
  518. /* ===== 弹窗检测 ===== */
  519. function isModalOpen() { return document.getElementById('modal-overlay').classList.contains('show'); }
  520. /* ===== 键盘事件 ===== */
  521. document.addEventListener('keydown', e => {
  522. if (isModalOpen()) return;
  523. if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); nextSlide(); }
  524. else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); prevSlide(); }
  525. else if (e.key === 'Home') { e.preventDefault(); goSlide(0); }
  526. else if (e.key === 'End') { e.preventDefault(); goSlide(totalSlides - 1); }
  527. });
  528. /* ===== 鼠标滚轮 ===== */
  529. let wheelTimeout = 0;
  530. document.addEventListener('wheel', e => {
  531. if (isModalOpen()) return;
  532. e.preventDefault();
  533. const now = Date.now();
  534. if (now - wheelTimeout < 800) return;
  535. wheelTimeout = now;
  536. e.deltaY > 0 ? nextSlide() : prevSlide();
  537. }, { passive: false });
  538. /* ===== 触屏滑动 ===== */
  539. let touchStartY = 0, touchStartX = 0;
  540. document.addEventListener('touchstart', e => { touchStartY = e.touches[0].clientY; touchStartX = e.touches[0].clientX; });
  541. document.addEventListener('touchend', e => {
  542. if (isModalOpen()) return;
  543. const dy = e.changedTouches[0].clientY - touchStartY;
  544. const dx = e.changedTouches[0].clientX - touchStartX;
  545. if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 50) { dy < 0 ? nextSlide() : prevSlide(); }
  546. else if (Math.abs(dx) > 50) { dx < 0 ? nextSlide() : prevSlide(); }
  547. });
  548. /* ===== 全屏 ===== */
  549. document.getElementById('fs-btn').addEventListener('click', () => {
  550. if (!document.fullscreenElement) document.documentElement.requestFullscreen().catch(() => {});
  551. else document.exitFullscreen();
  552. });
  553. /* ===== 数字递增动画 ===== */
  554. function animateCounters(slideEl) {
  555. slideEl.querySelectorAll('.metric-value[data-count]').forEach(el => {
  556. const target = +el.dataset.count;
  557. const prefix = el.dataset.prefix || '';
  558. const suffix = el.dataset.suffix || '';
  559. const duration = 1800;
  560. const start = performance.now();
  561. const format = n => {
  562. if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
  563. if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1) + 'K';
  564. return n.toLocaleString();
  565. };
  566. function step(now) {
  567. const p = Math.min((now - start) / duration, 1);
  568. const ease = 1 - Math.pow(1 - p, 3);
  569. el.textContent = prefix + format(Math.round(target * ease)) + suffix;
  570. if (p < 1) requestAnimationFrame(step);
  571. }
  572. requestAnimationFrame(step);
  573. });
  574. }
  575. /* ===== 进度条动画 ===== */
  576. function animateBars(slideEl) {
  577. slideEl.querySelectorAll('.bar-fill[data-target]').forEach(el => {
  578. setTimeout(() => { el.style.width = el.dataset.target + '%'; }, 200);
  579. });
  580. slideEl.querySelectorAll('.pain-fill[data-target]').forEach(el => {
  581. setTimeout(() => {
  582. el.style.width = (el.dataset.target / 35 * 100) + '%';
  583. if (el.dataset.bg) el.style.background = el.dataset.bg;
  584. }, 200);
  585. });
  586. }
  587. /* ===== 页面动画触发器 ===== */
  588. const animatedSlides = new Set();
  589. function triggerSlideAnimations(n) {
  590. const slideEl = document.getElementById('slide-' + n);
  591. if (!slideEl) return;
  592. if (!animatedSlides.has(n)) {
  593. animatedSlides.add(n);
  594. animateCounters(slideEl);
  595. animateBars(slideEl);
  596. initChartsForSlide(n);
  597. }
  598. }
  599. /* ============================================================
  600. Chart.js 全局配置
  601. ============================================================ */
  602. const chartInstances = {};
  603. const chartFontColor = '#F5E6D3';
  604. const chartGridColor = 'rgba(139,111,71,0.15)';
  605. const PALETTE = ['rgba(197,165,90,0.7)','rgba(143,166,138,0.65)','rgba(196,134,139,0.6)','rgba(126,155,181,0.55)','rgba(212,185,120,0.5)','rgba(139,111,71,0.4)'];
  606. const PALETTE_BORDER = ['#C5A55A','#8FA68A','#C4868B','#7E9BB5','#D4B978','#8B6F47'];
  607. Chart.defaults.color = chartFontColor;
  608. Chart.defaults.font.family = "'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif";
  609. Chart.defaults.font.size = 12;
  610. function initChartsForSlide(n) {
  611. if (n === 3) initMarketCharts();
  612. if (n === 4) initRadarChart();
  613. if (n === 5) initScenesChart();
  614. if (n === 6) initTiktokChart();
  615. }
  616. /* --- 市场全景图表 --- */
  617. function initMarketCharts() {
  618. if (chartInstances.revenue || !REPORT_DATA.categories) return;
  619. const cats = REPORT_DATA.categories;
  620. const labels = cats.map(c => c.name);
  621. const revenueData = cats.map(c => c.revenueNum);
  622. chartInstances.revenue = new Chart(document.getElementById('chart-revenue'), {
  623. type: 'bar',
  624. data: { labels, datasets: [{ data: revenueData, backgroundColor: PALETTE.slice(0, cats.length), borderColor: PALETTE_BORDER.slice(0, cats.length), borderWidth: 1, borderRadius: 6, maxBarThickness: 52 }] },
  625. options: {
  626. responsive: true, maintainAspectRatio: false,
  627. plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => '$' + (ctx.raw / 1e6).toFixed(1) + 'M / 周' } } },
  628. scales: { y: { ticks: { callback: v => '$' + (v/1e6).toFixed(0) + 'M' }, grid: { color: chartGridColor } }, x: { grid: { display: false } } },
  629. animation: { duration: 1200, easing: 'easeOutQuart' }
  630. }
  631. });
  632. const growthSorted = [...cats].sort((a,b) => b.growthNum - a.growthNum);
  633. chartInstances.growth = new Chart(document.getElementById('chart-growth'), {
  634. type: 'bar',
  635. data: {
  636. labels: growthSorted.map(c => c.name),
  637. datasets: [{ data: growthSorted.map(c => c.growthNum),
  638. backgroundColor: growthSorted.map(c => c.growthNum > 50 ? 'rgba(143,166,138,0.7)' : c.growthNum > 0 ? 'rgba(197,165,90,0.6)' : 'rgba(196,134,139,0.5)'),
  639. borderColor: growthSorted.map(c => c.growthNum > 50 ? '#8FA68A' : c.growthNum > 0 ? '#C5A55A' : '#C4868B'),
  640. borderWidth: 1, borderRadius: 6, maxBarThickness: 52 }]
  641. },
  642. options: {
  643. indexAxis: 'y', responsive: true, maintainAspectRatio: false,
  644. plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => (ctx.raw > 0 ? '+' : '') + ctx.raw + '%' } } },
  645. scales: { x: { ticks: { callback: v => (v > 0 ? '+' : '') + v + '%' }, grid: { color: chartGridColor } }, y: { grid: { display: false } } },
  646. animation: { duration: 1200, easing: 'easeOutQuart' }
  647. }
  648. });
  649. }
  650. /* --- 竞品雷达图 --- */
  651. function initRadarChart() {
  652. if (chartInstances.radar || !REPORT_DATA.competitors) return;
  653. const comps = REPORT_DATA.competitors;
  654. const radarLabels = ['品牌知名度','社媒粉丝','内容质量','产品线宽度','价格竞争力'];
  655. const colors = ['#C5A55A','#8FA68A','#C4868B','#7E9BB5','#D4B978'];
  656. const bgColors = ['rgba(197,165,90,0.15)','rgba(143,166,138,0.12)','rgba(196,134,139,0.1)','rgba(126,155,181,0.1)','rgba(212,185,120,0.1)'];
  657. chartInstances.radar = new Chart(document.getElementById('chart-radar'), {
  658. type: 'radar',
  659. data: {
  660. labels: radarLabels,
  661. datasets: comps.map((c,i) => ({
  662. label: c.name, data: c.radarData || [50,50,50,50,50],
  663. borderColor: colors[i%colors.length], backgroundColor: bgColors[i%bgColors.length],
  664. pointBackgroundColor: colors[i%colors.length], borderWidth: 2
  665. }))
  666. },
  667. options: {
  668. responsive: true, maintainAspectRatio: false,
  669. scales: { r: { min: 0, max: 100, ticks: { stepSize: 25, display: false }, grid: { color: chartGridColor }, pointLabels: { font: { size: 11 } }, angleLines: { color: chartGridColor } } },
  670. plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 14, font: { size: 11 } } } },
  671. animation: { duration: 1200 }
  672. }
  673. });
  674. }
  675. /* --- 使用场景饼图 --- */
  676. function initScenesChart() {
  677. if (chartInstances.scenes || !REPORT_DATA.vocAnalysis) return;
  678. const scenes = REPORT_DATA.vocAnalysis.scenes;
  679. chartInstances.scenes = new Chart(document.getElementById('chart-scenes'), {
  680. type: 'doughnut',
  681. data: {
  682. labels: scenes.map(s => s.name + ' ' + s.pct + '%'),
  683. datasets: [{ data: scenes.map(s => s.pct), backgroundColor: PALETTE.slice(0, scenes.length), borderColor: 'rgba(26,17,11,0.6)', borderWidth: 2 }]
  684. },
  685. options: {
  686. responsive: true, maintainAspectRatio: false, cutout: '55%',
  687. plugins: { legend: { position: 'right', labels: { boxWidth: 10, padding: 8, font: { size: 10 } } } },
  688. animation: { duration: 1200 }
  689. }
  690. });
  691. }
  692. /* --- TikTok热度图表 --- */
  693. function initTiktokChart() {
  694. if (chartInstances.tiktok || !REPORT_DATA.tiktokHeat) return;
  695. const tt = REPORT_DATA.tiktokHeat;
  696. chartInstances.tiktok = new Chart(document.getElementById('chart-tiktok'), {
  697. type: 'bar',
  698. data: {
  699. labels: tt.map(t => t.name),
  700. datasets: [{ label: '播放量', data: tt.map(t => t.plays),
  701. backgroundColor: PALETTE.slice(0, tt.length), borderColor: PALETTE_BORDER.slice(0, tt.length),
  702. borderWidth: 1, borderRadius: 6, maxBarThickness: 48 }]
  703. },
  704. options: {
  705. responsive: true, maintainAspectRatio: false,
  706. plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => (ctx.raw / 1e6).toFixed(1) + 'M 播放' } } },
  707. scales: { y: { ticks: { callback: v => (v/1e6).toFixed(0) + 'M' }, grid: { color: chartGridColor } }, x: { grid: { display: false } } },
  708. animation: { duration: 1200, easing: 'easeOutQuart' }
  709. }
  710. });
  711. }
  712. /* ============================================================
  713. 数据驱动 DOM 渲染
  714. ============================================================ */
  715. function renderReport() {
  716. const D = REPORT_DATA;
  717. if (!D || !D.categories) { console.warn('REPORT_DATA 未注入'); return; }
  718. // --- Slide 2: 执行摘要 ---
  719. if (D.metrics) {
  720. document.getElementById('exec-metrics').innerHTML = D.metrics.map(m =>
  721. `<div class="glass-card metric-card"><div class="metric-value" data-count="${m.value}" data-prefix="${m.prefix||''}" data-suffix="${m.suffix||''}">${m.prefix||''}0${m.suffix||''}</div><div class="metric-label">${m.label}</div><div class="metric-sub">${m.sub}</div></div>`
  722. ).join('');
  723. }
  724. if (D.coreFindings) {
  725. document.getElementById('core-findings').innerHTML = D.coreFindings.map(f =>
  726. `<li style="margin-bottom:8px">• <strong class="${f.highlightClass||'text-gold'}">${f.highlight||''}</strong> ${f.text}</li>`
  727. ).join('');
  728. }
  729. if (D.coreValues) {
  730. document.getElementById('core-values').innerHTML = D.coreValues.map(v =>
  731. `<li style="margin-bottom:8px">• ${v}</li>`
  732. ).join('');
  733. }
  734. // --- Slide 3: 市场全景表格 ---
  735. if (D.categories) {
  736. document.getElementById('market-table-body').innerHTML = D.categories.map(c => {
  737. const growthClass = c.growthNum > 30 ? 'text-sage fw-700' : c.growthNum > 0 ? 'text-sage' : 'text-rose';
  738. const chinaClass = c.chinaRatioNum > 70 ? 'text-rose fw-600' : '';
  739. return `<tr><td class="fw-600 ${c.highlight?'text-gold':''}">${c.name}${c.highlight?' ⭐':''}</td><td>${c.weeklySales}</td><td>${c.weeklyRevenue}</td><td>${c.avgPrice}</td><td>${c.brands}</td><td class="${growthClass}">${c.growth}</td><td class="${chinaClass}">${c.chinaRatio}</td></tr>`;
  740. }).join('');
  741. }
  742. // --- Slide 4: 竞品卡片 ---
  743. if (D.competitors) {
  744. document.getElementById('competitor-cards').innerHTML = D.competitors.map(c => `
  745. <div class="glass-card competitor-card">
  746. <span class="badge ${c.positionBadge||'badge-gold'} mb-sm" style="display:inline-block">${c.positioning}</span>
  747. <h3>${c.name}</h3>
  748. <p class="text-xs text-dim mb-sm">${c.priceRange||''}</p>
  749. <div class="comp-stat"><span>Instagram</span><span class="fw-700 text-gold">${c.igFollowers||'—'}</span></div>
  750. <div class="comp-stat"><span>TikTok</span><span class="fw-600">${c.tiktokFollowers||'—'}</span></div>
  751. <div class="comp-stat"><span>Amazon SKU</span><span>${c.skuNote||'—'}</span></div>
  752. <div class="comp-stat"><span>策略</span><span class="text-xs">${c.strategyNote||'—'}</span></div>
  753. <p class="text-xs mt-sm text-dim">${c.pros||''}<br>${c.cons||''}</p>
  754. </div>
  755. `).join('');
  756. }
  757. // --- Slide 5: VOC ---
  758. if (D.vocAnalysis) {
  759. const voc = D.vocAnalysis;
  760. document.getElementById('voc-review-count').textContent = voc.totalReviews + '条';
  761. document.getElementById('pain-points-container').innerHTML = voc.painPoints.map(p =>
  762. `<div class="pain-bar"><div class="pain-icon">${p.icon}</div><div class="pain-info"><div class="pain-name">${p.name} <span class="badge badge-rose">${p.pct}%</span></div><div class="pain-track"><div class="pain-fill" style="width:0%" data-target="${p.pct}" data-bg="${p.gradient}"></div></div>${p.quote?`<div class="pain-pct">"${p.quote}"</div>`:''}</div></div>`
  763. ).join('');
  764. document.getElementById('highlights-container').innerHTML = voc.highlights.map(h =>
  765. `<div class="bar-row"><span class="bar-label">${h.name}</span><div class="bar-track"><div class="bar-fill" style="width:0%;background:${h.color}" data-target="${h.pct}" data-value="${h.pct}%"></div></div></div>`
  766. ).join('');
  767. }
  768. // --- Slide 6: 品类矩阵 ---
  769. if (D.categoryMatrix) {
  770. document.getElementById('matrix-table-body').innerHTML = D.categoryMatrix.map(c =>
  771. `<tr><td class="fw-600 ${c.highlight?'text-gold':''}">${c.name}</td><td>${c.marketSize}</td><td>${c.growth}</td><td>${c.socialHeat}</td><td>${c.competition}</td><td>${c.chinaRatio}</td><td><span class="badge ${c.badgeClass||'badge-gold'}">${c.recommendation}</span></td></tr>`
  772. ).join('');
  773. }
  774. if (D.seasonalNotes) {
  775. document.getElementById('seasonality-notes').innerHTML = D.seasonalNotes.map(s =>
  776. `<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px"><span style="font-size:1.4rem">${s.icon}</span><div><strong class="text-gold">${s.title}</strong><br>${s.desc}</div></div>`
  777. ).join('');
  778. }
  779. // --- Slide 7: 执行策略 ---
  780. if (D.strategy) {
  781. const phases = [
  782. { key: 'shortTerm', label: '短期 1-3个月', badge: 'badge-sage' },
  783. { key: 'midTerm', label: '中期 3-6个月', badge: 'badge-gold' },
  784. { key: 'longTerm', label: '长期 6-12个月', badge: 'badge-blue' }
  785. ];
  786. document.getElementById('strategy-columns').innerHTML = phases.map(p => `
  787. <div class="glass-card">
  788. <span class="badge ${p.badge} mb-sm" style="display:inline-block">${p.label}</span>
  789. <div class="timeline mt-sm">
  790. ${(D.strategy[p.key]||[]).map(item => `<div class="timeline-item"><h4>${item.title}</h4><p>${item.desc}</p></div>`).join('')}
  791. </div>
  792. </div>
  793. `).join('');
  794. }
  795. if (D.vocPriorities) {
  796. document.getElementById('voc-priorities').innerHTML = D.vocPriorities.map(p =>
  797. `<span class="badge ${p.badgeClass||'badge-rose'}">${p.text}</span>`
  798. ).join('');
  799. }
  800. // --- Slide 8: 总结 ---
  801. if (D.conclusion) {
  802. const con = D.conclusion;
  803. document.getElementById('conclusion-top-cards').innerHTML = `
  804. <div class="glass-card" style="text-align:center;padding:32px 24px"><div style="font-size:2.4rem;margin-bottom:8px">${con.topCategory.emoji}</div><h3 class="text-gold mb-sm">${con.topCategory.title}</h3><p class="text-sm"><strong>${con.topCategory.name}</strong></p><p class="text-xs text-dim mt-sm">${con.topCategory.desc}</p></div>
  805. <div class="glass-card" style="text-align:center;padding:32px 24px"><div style="font-size:2.4rem;margin-bottom:8px">${con.socialDividend.emoji}</div><h3 class="text-gold mb-sm">${con.socialDividend.title}</h3><p class="text-sm"><strong>${con.socialDividend.name}</strong></p><p class="text-xs text-dim mt-sm">${con.socialDividend.desc}</p></div>`;
  806. document.getElementById('conclusion-differentiator').innerHTML =
  807. `<h3 class="text-gold mb-sm">${con.differentiator.title}</h3><p style="font-size:1.15rem;font-weight:600;color:var(--cream)">${con.differentiator.subtitle}</p><p class="text-sm text-dim mt-sm">${con.differentiator.desc}</p>`;
  808. document.getElementById('conclusion-actions').innerHTML =
  809. `<h3 class="text-sm fw-600 mb-sm" style="text-align:center">✅ 三大关键行动</h3><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;text-align:center;font-size:.85rem">${con.keyActions.map(a => `<div><div style="font-size:1.6rem;margin-bottom:4px">${a.emoji}</div><strong>${a.title}</strong><br><span class="text-xs text-dim">${a.desc}</span></div>`).join('')}</div>`;
  810. }
  811. }
  812. /* ============================================================
  813. 封面粒子效果
  814. ============================================================ */
  815. function initParticles() {
  816. const canvas = document.getElementById('particles-canvas');
  817. if (!canvas) return;
  818. const ctx = canvas.getContext('2d');
  819. let w, h, particles = [];
  820. function resize() { w = canvas.width = window.innerWidth; h = canvas.height = window.innerHeight; }
  821. resize();
  822. window.addEventListener('resize', resize);
  823. for (let i = 0; i < 60; i++) {
  824. particles.push({
  825. x: Math.random() * w, y: Math.random() * h,
  826. r: Math.random() * 2 + 0.5,
  827. dx: (Math.random() - 0.5) * 0.3, dy: (Math.random() - 0.5) * 0.2,
  828. alpha: Math.random() * 0.4 + 0.1,
  829. color: Math.random() > 0.5 ? '197,165,90' : '212,185,120'
  830. });
  831. }
  832. function draw() {
  833. ctx.clearRect(0, 0, w, h);
  834. const grd = ctx.createRadialGradient(w*.5, h*.4, 0, w*.5, h*.4, w*.6);
  835. grd.addColorStop(0, 'rgba(92,61,46,0.12)');
  836. grd.addColorStop(0.5, 'rgba(45,24,16,0.06)');
  837. grd.addColorStop(1, 'transparent');
  838. ctx.fillStyle = grd; ctx.fillRect(0, 0, w, h);
  839. particles.forEach(p => {
  840. ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2);
  841. ctx.fillStyle = `rgba(${p.color},${p.alpha})`; ctx.fill();
  842. p.x += p.dx; p.y += p.dy;
  843. if (p.x<0||p.x>w) p.dx *= -1;
  844. if (p.y<0||p.y>h) p.dy *= -1;
  845. p.alpha += (Math.random()-.5)*.01;
  846. p.alpha = Math.max(.05, Math.min(.5, p.alpha));
  847. });
  848. for (let i=0; i<particles.length; i++) {
  849. for (let j=i+1; j<particles.length; j++) {
  850. const dx=particles[i].x-particles[j].x, dy=particles[i].y-particles[j].y;
  851. const dist = Math.sqrt(dx*dx+dy*dy);
  852. if (dist<150) { ctx.beginPath(); ctx.moveTo(particles[i].x,particles[i].y); ctx.lineTo(particles[j].x,particles[j].y); ctx.strokeStyle=`rgba(197,165,90,${.06*(1-dist/150)})`; ctx.lineWidth=.5; ctx.stroke(); }
  853. }
  854. }
  855. requestAnimationFrame(draw);
  856. }
  857. draw();
  858. }
  859. /* ============================================================
  860. Modal 弹窗系统
  861. ============================================================ */
  862. const modalOverlay = document.getElementById('modal-overlay');
  863. const modalHeader = document.getElementById('modal-header');
  864. const modalBody = document.getElementById('modal-body');
  865. function openModal(headerHTML, bodyHTML) {
  866. modalHeader.innerHTML = headerHTML;
  867. modalBody.innerHTML = bodyHTML;
  868. modalOverlay.style.display = 'flex';
  869. requestAnimationFrame(() => modalOverlay.classList.add('show'));
  870. }
  871. function closeModal() {
  872. modalOverlay.classList.remove('show');
  873. setTimeout(() => { modalOverlay.style.display = 'none'; }, 350);
  874. }
  875. document.getElementById('modal-close').addEventListener('click', closeModal);
  876. modalOverlay.addEventListener('click', e => { if(e.target===modalOverlay) closeModal(); });
  877. document.addEventListener('keydown', e => { if(e.key==='Escape' && modalOverlay.classList.contains('show')) closeModal(); });
  878. /* ===== 初始化 ===== */
  879. renderReport();
  880. initParticles();
  881. triggerSlideAnimations(0);
  882. </script>
  883. </body>
  884. </html>