hongcheng-render-v3.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const HA = require('./hongcheng-analyze.js');
  5. const ROOT = path.resolve(__dirname, '..', '..');
  6. const OUT_FILE = path.join(ROOT, 'reports', 'hongcheng-voc-insight-report-v3.html');
  7. const data = HA.loadMerged();
  8. const meta = HA.getMeta(data);
  9. const items = (data && data.items) || [];
  10. function esc(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
  11. function fmtLikes(n) { if (n == null) return '0'; if (n >= 10000) return (n/10000).toFixed(1).replace(/\.0$/,'')+'w'; if (n >= 1000) return (n/1000).toFixed(1).replace(/\.0$/,'')+'k'; return String(n); }
  12. function truncate(s, n=180) { s = String(s ?? ''); return s.length > n ? s.slice(0,n-1)+'…' : s; }
  13. const hypos = [
  14. {h:'H1',title:'医院地推是高效场景',count:meta.hypotheses?.H1||0,color:'#F5A623'},
  15. {h:'H2',title:'价格透明是转化关键',count:meta.hypotheses?.H2||0,color:'#00DC82'},
  16. {h:'H3',title:'短剧提升品牌认知',count:meta.hypotheses?.H3||0,color:'#8B5CF6'},
  17. {h:'H4',title:'专业度信任需要证明',count:meta.hypotheses?.H4||0,color:'#FF4D8D'},
  18. {h:'H5',title:'社区店是主要威胁',count:meta.hypotheses?.H5||0,color:'#3B82F6'},
  19. {h:'H6',title:'老带新是低成本渠道',count:meta.hypotheses?.H6||0,color:'#EF4444'},
  20. {h:'H7',title:'美团小红书是搜索主力',count:meta.hypotheses?.H7||0,color:'#F97316'},
  21. {h:'H8',title:'不满意能换是保障',count:meta.hypotheses?.H8||0,color:'#6B6B6B'},
  22. ];
  23. const competitors = [
  24. {name:'天鹅到家',revenue:'28-32亿',strength:'全国第一/平台模式/AI驱动',借鉴:'标准化体系+多渠道组合',color:'#3B82F6'},
  25. {name:'好孕妈妈',revenue:'18-22亿',strength:'医疗背景/医院合作强/CRM',借鉴:'医院深度合作是最高效场景',color:'#00DC82'},
  26. {name:'多喜娃',revenue:'3-4.5亿',strength:'深圳第二/自营+医院地推+美团',借鉴:'自营模式+医院地推三步走',color:'#F97316'},
  27. {name:'妈咪无忧',revenue:'7-9亿',strength:'上海第一/小红书KOS矩阵',借鉴:'账号矩阵打法',color:'#FF4D8D'},
  28. ];
  29. const geoData = [
  30. {label:'广东',value:451},{label:'江苏',value:394},{label:'山东',value:273},
  31. {label:'浙江',value:245},{label:'湖南',value:232},{label:'广西',value:209},
  32. {label:'四川',value:191},{label:'河南',value:181},{label:'安徽',value:163},
  33. ];
  34. const kwData = [
  35. {label:'新手爸妈',value:1184,color:'#FF4D8D'},{label:'好孕妈妈月嫂',value:1142,color:'#00DC82'},
  36. {label:'坐月子注意事项',value:965,color:'#F5A623'},{label:'新手爸妈找月嫂',value:734,color:'#FF4D8D'},
  37. {label:'月嫂多少钱一个月',value:408,color:'#F5A623'},{label:'天鹅到家月嫂',value:134,color:'#3B82F6'},
  38. ];
  39. function getTop(sentiment, n=6) {
  40. return items.filter(it => it.sentiment === sentiment && it.content && it.content.length > 5)
  41. .sort((a,b) => (b.likes||0)-(a.likes||0)).slice(0,n);
  42. }
  43. const positives = getTop('positive', 4);
  44. const neutrals = getTop('neutral', 4);
  45. const negatives = getTop('negative', 4);
  46. function vocCard(item) {
  47. const senColor = {positive:'#00DC82',neutral:'#6B6B6B',negative:'#EF4444',conflicted:'#F97316'}[item.sentiment]||'#6B6B6B';
  48. return `<div style="background:linear-gradient(180deg,rgba(255,255,255,0.03) 0%,rgba(255,255,255,0.01) 100%),var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 22px;backdrop-filter:blur(8px);">
  49. <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap;">
  50. <div style="display:flex;align-items:center;gap:8px;">
  51. <span style="width:22px;height:22px;border-radius:50%;background:#1A1A1A;color:#fff;font-size:0.6rem;font-weight:700;display:flex;align-items:center;justify-content:center;">抖</span>
  52. <span style="font-weight:600;font-size:0.85rem;">${esc(item.nickname||'匿名')}</span>
  53. ${item.ip?`<span style="font-size:0.72rem;color:var(--text-3);">· ${esc(item.ip)}</span>`:''}
  54. </div>
  55. <div style="display:flex;align-items:center;gap:8px;">
  56. <span style="font-size:0.72rem;font-weight:600;color:${senColor};">${item.sentiment==='positive'?'▲':item.sentiment==='negative'?'▼':'●'} ${item.sentiment}</span>
  57. <span style="font-size:0.72rem;color:var(--text-3);font-family:var(--font-mono);">♥${fmtLikes(item.likes)}</span>
  58. </div>
  59. </div>
  60. <div style="font-size:0.92rem;line-height:1.65;color:var(--text-1);">"${esc(truncate(item.content,200))}"</div>
  61. ${Array.isArray(item.tags)&&item.tags.length?`<div style="margin-top:10px;display:flex;flex-wrap:wrap;gap:5px;">${item.tags.slice(0,3).map(t=>`<span style="padding:2px 8px;background:${senColor}18;color:${senColor};border-radius:3px;font-size:0.65rem;font-weight:500;">${esc(t)}</span>`).join('')}</div>`:''}
  62. </div>`;
  63. }
  64. function barChart(rows, accent) {
  65. const max = Math.max(...rows.map(r=>r.value),1);
  66. const barH=36,gap=14,padL=140,padR=100,padV=16,W=900;
  67. const totalH = rows.length*(barH+gap)+padV*2;
  68. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;">
  69. <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
  70. ${rows.map((r,i)=>{const y=padV+i*(barH+gap);const w=Math.max((r.value/max)*(W-padL-padR),6);return `<text x="${padL-10}" y="${y+barH/2+5}" text-anchor="end" font-size="13" fill="var(--text-2)">${esc(r.label)}</text><rect x="${padL}" y="${y}" width="${w}" height="${barH}" rx="4" fill="${r.color||accent}" opacity="0.85"/><text x="${padL+w+10}" y="${y+barH/2+5}" font-size="13" font-weight="700" fill="${r.color||accent}" font-family="monospace">${esc(r.valueLabel||r.value)}</text>`;}).join('')}
  71. </svg>
  72. </div>`;
  73. }
  74. const html = `<!DOCTYPE html>
  75. <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
  76. <title>洪城到家 · VOC 深度洞察报告</title>
  77. <style>
  78. :root{--bg-0:#0A0A0A;--bg-1:#111111;--bg-2:#1A1A1A;--bg-3:#252525;--border:rgba(255,255,255,0.08);--border-strong:rgba(255,255,255,0.16);--text-1:#F5F5F5;--text-2:#A8A8A8;--text-3:#6B6B6B;--amber:#F5A623;--amber-glow:rgba(245,166,35,0.15);--green:#00DC82;--purple:#8B5CF6;--rose:#FF4D8D;--blue:#3B82F6;--red:#EF4444;--orange:#F97316;--font-head:'Inter','Noto Sans SC','PingFang SC','Microsoft YaHei',sans-serif;--font-body:'Noto Sans SC','Inter','PingFang SC','Microsoft YaHei',sans-serif;--font-mono:'JetBrains Mono','SF Mono','Fira Code',monospace;--max-w:1280px;--radius-sm:8px;--radius-md:12px;--radius-lg:20px;}
  79. *{margin:0;padding:0;box-sizing:border-box}
  80. html{scroll-behavior:smooth;font-size:16px;scroll-snap-type:y proximity;}
  81. body{font-family:var(--font-body);background:var(--bg-0);color:var(--text-1);line-height:1.65;-webkit-font-smoothing:antialiased;overflow-x:hidden}
  82. body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;background:radial-gradient(ellipse 900px 700px at var(--amb-x,20%) var(--amb-y,25%),rgba(245,166,35,0.10),transparent 60%),radial-gradient(ellipse 800px 600px at calc(100% - var(--amb-x,20%)) calc(100% - var(--amb-y,25%)),rgba(139,92,246,0.07),transparent 60%);transition:background 1.4s cubic-bezier(.2,.7,.2,1);}
  83. body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;background-image:linear-gradient(rgba(255,255,255,0.015) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.015) 1px,transparent 1px);background-size:80px 80px;mask-image:radial-gradient(ellipse 90% 95% at 50% 50%,black 0%,transparent 85%);-webkit-mask-image:radial-gradient(ellipse 90% 95% at 50% 50%,black 0%,transparent 85%);}
  84. main.deck{position:relative;z-index:1}
  85. ::selection{background:var(--amber);color:#000}
  86. .text-hero{font-family:var(--font-head);font-weight:900;letter-spacing:-0.04em;line-height:0.95;font-size:clamp(4rem,12vw,10rem)}
  87. .text-poster{font-family:var(--font-head);font-weight:800;letter-spacing:-0.03em;line-height:1.05;font-size:clamp(2.5rem,6vw,5rem)}
  88. .text-h1{font-family:var(--font-head);font-weight:700;letter-spacing:-0.02em;line-height:1.1;font-size:clamp(2rem,4vw,3.5rem)}
  89. .text-h2{font-family:var(--font-head);font-weight:700;letter-spacing:-0.015em;line-height:1.2;font-size:clamp(1.4rem,2.5vw,2rem)}
  90. .text-h3{font-weight:600;font-size:clamp(1.05rem,1.6vw,1.3rem);letter-spacing:-0.01em}
  91. .text-body{font-size:clamp(0.92rem,1.1vw,1rem);line-height:1.75;color:var(--text-2)}
  92. .text-sm{font-size:0.85rem;color:var(--text-2)}
  93. .text-xs{font-size:0.72rem;color:var(--text-3);letter-spacing:0.02em}
  94. .text-mono{font-family:var(--font-mono)}
  95. .text-amber{color:var(--amber)}.text-green{color:var(--green)}.text-purple{color:var(--purple)}.text-rose{color:var(--rose)}.text-blue{color:var(--blue)}.text-red{color:var(--red)}.text-orange{color:var(--orange)}
  96. section.report-section{min-height:100vh;padding:clamp(60px,10vh,120px) clamp(24px,6vw,80px);position:relative;border-bottom:1px solid var(--border);display:flex;flex-direction:column;justify-content:center;scroll-snap-align:start;scroll-snap-stop:always;overflow:hidden;isolation:isolate}
  97. section.report-section::before{content:attr(data-slide-num);position:absolute;top:clamp(20px,3vh,36px);right:clamp(24px,4vw,60px);font-family:var(--font-mono);font-weight:600;font-size:clamp(0.68rem,0.75vw,0.78rem);letter-spacing:0.25em;color:var(--text-3);z-index:4;pointer-events:none}
  98. section.report-section::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:-1;background:radial-gradient(ellipse 70% 60% at 85% 15%,var(--slide-glow-1,rgba(245,166,35,0.06)),transparent 55%),radial-gradient(ellipse 60% 50% at 15% 85%,var(--slide-glow-2,rgba(139,92,246,0.04)),transparent 55%);opacity:0;transition:opacity 1s cubic-bezier(.2,.7,.2,1)}
  99. section.report-section.in-view::after{opacity:1}
  100. section.report-section[data-tone="amber"]{--slide-glow-1:rgba(245,166,35,0.10);--slide-glow-2:rgba(245,166,35,0.04)}
  101. section.report-section[data-tone="rose"]{--slide-glow-1:rgba(255,77,141,0.09);--slide-glow-2:rgba(255,77,141,0.04)}
  102. section.report-section[data-tone="green"]{--slide-glow-1:rgba(0,220,130,0.08);--slide-glow-2:rgba(0,220,130,0.03)}
  103. section.report-section[data-tone="purple"]{--slide-glow-1:rgba(139,92,246,0.10);--slide-glow-2:rgba(139,92,246,0.04)}
  104. section.report-section[data-tone="blue"]{--slide-glow-1:rgba(59,130,246,0.08);--slide-glow-2:rgba(59,130,246,0.03)}
  105. section.report-section[data-tone="orange"]{--slide-glow-1:rgba(249,115,22,0.10);--slide-glow-2:rgba(249,115,22,0.04)}
  106. .section-inner{width:100%;max-width:var(--max-w);margin:0 auto;position:relative}
  107. .section-head{margin-bottom:clamp(32px,5vh,64px)}
  108. .section-eyebrow{display:inline-flex;align-items:center;gap:10px;font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.18em;text-transform:uppercase;color:var(--amber);font-weight:500;margin-bottom:18px}
  109. .section-eyebrow::before{content:'';display:inline-block;width:24px;height:1px;background:var(--amber)}
  110. .section-sub{color:var(--text-2);font-size:1.05rem;max-width:720px;margin-top:14px;line-height:1.8}
  111. .grid{display:grid;gap:clamp(14px,1.6vw,22px)}
  112. .grid-2{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
  113. .grid-3{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
  114. .grid-4{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
  115. .card{background:linear-gradient(180deg,rgba(255,255,255,0.025) 0%,rgba(255,255,255,0.008) 100%),var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(20px,2.2vw,32px);position:relative;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:transform 0.4s cubic-bezier(.2,.7,.2,1),border-color 0.3s ease,box-shadow 0.4s ease}
  116. .card:hover{border-color:var(--border-strong);transform:translateY(-3px);box-shadow:0 12px 40px rgba(0,0,0,0.35),0 0 0 1px rgba(245,166,35,0.08)}
  117. .card.card-glow{box-shadow:0 0 40px rgba(245,166,35,0.06)}
  118. .card-accent{border-left:3px solid var(--amber);border-top:1px solid var(--border);border-right:1px solid var(--border);border-bottom:1px solid var(--border)}
  119. .metric{background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(24px,2.5vw,36px);position:relative;overflow:hidden}
  120. .metric::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--amber),transparent);opacity:0.6}
  121. .metric .metric-label{font-family:var(--font-mono);font-size:0.7rem;letter-spacing:0.15em;text-transform:uppercase;color:var(--text-3);margin-bottom:10px}
  122. .metric .metric-value{font-family:var(--font-head);font-weight:800;font-size:clamp(2.2rem,4vw,3.5rem);letter-spacing:-0.03em;line-height:1;color:var(--text-1);margin-bottom:8px}
  123. .metric .metric-unit{font-family:var(--font-mono);font-size:0.85rem;color:var(--amber);font-weight:500}
  124. .metric .metric-sub{margin-top:14px;font-size:0.82rem;color:var(--text-2);line-height:1.5}
  125. .tag{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:4px;font-size:0.72rem;font-weight:500;letter-spacing:0.02em;font-family:var(--font-head);background:rgba(245,166,35,0.1);color:var(--amber);border:1px solid rgba(245,166,35,0.22)}
  126. .tag.green{background:rgba(0,220,130,0.08);color:var(--green);border-color:rgba(0,220,130,0.2)}
  127. .tag.purple{background:rgba(139,92,246,0.08);color:var(--purple);border-color:rgba(139,92,246,0.2)}
  128. .tag.rose{background:rgba(255,77,141,0.08);color:var(--rose);border-color:rgba(255,77,141,0.2)}
  129. .tag.blue{background:rgba(59,130,246,0.08);color:var(--blue);border-color:rgba(59,130,246,0.2)}
  130. .tag.orange{background:rgba(249,115,22,0.08);color:var(--orange);border-color:rgba(249,115,22,0.2)}
  131. .cover-hero{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;padding:clamp(40px,8vh,100px) clamp(24px,6vw,80px);background:radial-gradient(ellipse 60% 80% at 30% 30%,rgba(245,166,35,0.08) 0%,transparent 70%),radial-gradient(ellipse 40% 60% at 80% 70%,rgba(139,92,246,0.05) 0%,transparent 70%),var(--bg-0)}
  132. .cover-grid-bg{position:absolute;inset:0;background-image:linear-gradient(rgba(255,255,255,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.025) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 0%,transparent 70%);-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 0%,transparent 70%)}
  133. .footer{text-align:center;padding:40px 24px;color:var(--text-3);font-size:0.8rem;border-top:1px solid var(--border)}
  134. </style>
  135. </head>
  136. <body>
  137. <main class="deck">
  138. <!-- 封面 -->
  139. <section class="report-section" data-slide-num="COVER" data-tone="amber" style="position:relative;">
  140. <div class="cover-grid-bg"></div>
  141. <div class="cover-hero">
  142. <div class="section-eyebrow" style="margin-bottom:24px;">VOC 深度洞察报告</div>
  143. <h1 class="text-hero" style="margin-bottom:20px;">洪城到家</h1>
  144. <p class="text-body" style="font-size:1.2rem;max-width:600px;margin-bottom:24px;">从 3000 单到 3万单 · 江西母婴服务市场增长策略</p>
  145. <div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:32px;">
  146. <span class="tag">抖音</span>
  147. <span class="tag green">${meta.comments.toLocaleString()} 条真实评论</span>
  148. <span class="tag purple">10+ 竞品覆盖</span>
  149. </div>
  150. <div class="text-xs" style="margin-top:20px;">${meta.collectedAt || new Date().toISOString().slice(0,10)} · Generated by OpenClaw VOC System</div>
  151. </div>
  152. </section>
  153. <!-- Ch1: 执行摘要 -->
  154. <section class="report-section" data-slide-num="01" data-tone="amber">
  155. <div class="section-inner">
  156. <div class="section-head">
  157. <div class="section-eyebrow">CHAPTER 01</div>
  158. <h2 class="text-h1">执行摘要</h2>
  159. <p class="section-sub">核心问题与机会分析</p>
  160. </div>
  161. <div class="grid grid-4" style="margin-bottom:32px;">
  162. <div class="metric"><div class="metric-label">VOC总量</div><div class="metric-value" style="color:var(--amber);">${meta.comments.toLocaleString()}</div><div class="metric-sub">条真实评论</div></div>
  163. <div class="metric"><div class="metric-label">竞品覆盖</div><div class="metric-value" style="color:var(--green);">10+</div><div class="metric-sub">头部品牌</div></div>
  164. <div class="metric"><div class="metric-label">正面率</div><div class="metric-value" style="color:var(--green);">18%</div><div class="metric-sub">情感正向</div></div>
  165. <div class="metric"><div class="metric-label">目标市场</div><div class="metric-value" style="color:var(--rose);">10%</div><div class="metric-sub">江西30万新生儿/年</div></div>
  166. </div>
  167. <div class="card card-accent" style="margin-bottom:28px;padding:28px 32px;">
  168. <div class="text-amber" style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;margin-bottom:14px;font-weight:600;">核心问题</div>
  169. <div class="text-h2" style="margin-bottom:12px;">现状:3000单/年 → 目标:3万单/年(10倍增长)</div>
  170. <p class="text-body">江西30万新生儿/年,洪诚到家目前市场渗透率仅1%,目标吃下10%市场</p>
  171. </div>
  172. <div class="grid grid-3">
  173. <div class="card" style="border-left:3px solid var(--amber);">
  174. <div style="font-size:1.6rem;margin-bottom:12px;">1️⃣</div>
  175. <div style="font-weight:700;margin-bottom:8px;">抄作业</div>
  176. <p class="text-sm">学妈咪无忧小红书矩阵 + 学多喜娃医院地推 + 学好孕妈妈朋友圈投放 + 学天鹅到家标准化</p>
  177. </div>
  178. <div class="card" style="border-left:3px solid var(--orange);">
  179. <div style="font-size:1.6rem;margin-bottom:12px;">2️⃣</div>
  180. <div style="font-weight:700;margin-bottom:8px;">短剧破圈</div>
  181. <p class="text-sm">借南昌万亿短剧市场政策东风,低成本实现品牌曝光和流量导流</p>
  182. </div>
  183. <div class="card" style="border-left:3px solid var(--green);">
  184. <div style="font-size:1.6rem;margin-bottom:12px;">3️⃣</div>
  185. <div style="font-weight:700;margin-bottom:8px;">VOC驱动</div>
  186. <p class="text-sm">围绕专业度、价格透明度、服务保障三大痛点构建差异化价值</p>
  187. </div>
  188. </div>
  189. </div>
  190. </section>
  191. <!-- Ch2: 竞品获客策略 -->
  192. <section class="report-section" data-slide-num="02" data-tone="blue">
  193. <div class="section-inner">
  194. <div class="section-head">
  195. <div class="section-eyebrow" style="color:var(--blue);">CHAPTER 02</div>
  196. <h2 class="text-h1">竞品获客策略</h2>
  197. <p class="section-sub">头部品牌VOC拆解与可借鉴点</p>
  198. </div>
  199. <div class="grid grid-2" style="margin-bottom:28px;">
  200. ${competitors.map(c=>`<div class="card" style="border-top:2px solid ${c.color};">
  201. <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:14px;flex-wrap:wrap;gap:10px;">
  202. <div style="font-weight:700;font-size:1.1rem;">${c.name}</div>
  203. <span class="tag" style="background:${c.color}18;color:${c.color};border-color:${c.color}40;">${c.revenue}/年</span>
  204. </div>
  205. <p class="text-sm" style="margin-bottom:12px;">${c.strength}</p>
  206. <div style="padding:10px 14px;background:${c.color}10;border-radius:6px;font-size:0.82rem;">
  207. <span style="color:${c.color};font-weight:600;">→ </span>${c.借鉴}
  208. </div>
  209. </div>`).join('')}
  210. </div>
  211. <div style="margin-bottom:20px;">
  212. <div class="section-eyebrow" style="color:var(--blue);margin-bottom:16px;">抖音竞品关键词覆盖(VOC条数)</div>
  213. ${barChart(kwData.slice(0,5),'#3B82F6')}
  214. </div>
  215. </div>
  216. </section>
  217. <!-- Ch3: 用户痛点VOC -->
  218. <section class="report-section" data-slide-num="03" data-tone="rose">
  219. <div class="section-inner">
  220. <div class="section-head">
  221. <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 03</div>
  222. <h2 class="text-h1">用户痛点VOC</h2>
  223. <p class="section-sub">选择月嫂的三大顾虑</p>
  224. </div>
  225. <div class="grid grid-3" style="margin-bottom:32px;">
  226. <div class="card" style="border-top:2px solid var(--amber);">
  227. <div style="font-size:2rem;margin-bottom:12px;">💰</div>
  228. <div style="font-weight:700;color:var(--amber);margin-bottom:8px;">价格透明</div>
  229. <p class="text-sm" style="margin-bottom:10px;">"月嫂多少钱一个月"</p>
  230. <div class="text-xs">408条相关讨论</div>
  231. </div>
  232. <div class="card" style="border-top:2px solid var(--green);">
  233. <div style="font-size:2rem;margin-bottom:12px;">🏆</div>
  234. <div style="font-weight:700;color:var(--green);margin-bottom:8px;">专业度信任</div>
  235. <p class="text-sm" style="margin-bottom:10px;">"怎么知道月嫂靠不靠谱"</p>
  236. <div class="text-xs">H4假设: ${(meta.hypotheses?.H4||0).toLocaleString()}条覆盖</div>
  237. </div>
  238. <div class="card" style="border-top:2px solid var(--orange);">
  239. <div style="font-size:2rem;margin-bottom:12px;">🛡️</div>
  240. <div style="font-weight:700;color:var(--orange);margin-bottom:8px;">服务保障</div>
  241. <p class="text-sm" style="margin-bottom:10px;">"不满意能换吗"</p>
  242. <div class="text-xs">H8假设: ${(meta.hypotheses?.H8||0).toLocaleString()}条覆盖</div>
  243. </div>
  244. </div>
  245. <div style="margin-bottom:24px;">
  246. <div class="section-eyebrow" style="color:var(--green);margin-bottom:14px;">▲ 正面评价样本</div>
  247. <div class="grid grid-2">${positives.map(v=>vocCard(v)).join('')}</div>
  248. </div>
  249. <div>
  250. <div class="section-eyebrow" style="color:var(--red);margin-bottom:14px;">▼ 负面评价样本(需关注)</div>
  251. <div class="grid grid-2">${negatives.map(v=>vocCard(v)).join('')}</div>
  252. </div>
  253. </div>
  254. </section>
  255. <!-- Ch4: 假设验证 -->
  256. <section class="report-section" data-slide-num="04" data-tone="purple">
  257. <div class="section-inner">
  258. <div class="section-head">
  259. <div class="section-eyebrow" style="color:var(--purple);">CHAPTER 04</div>
  260. <h2 class="text-h1">H1-H8 假设验证</h2>
  261. <p class="section-sub">基于${meta.comments.toLocaleString()}条VOC的假设覆盖分析</p>
  262. </div>
  263. ${barChart(hypos.map(h=>({label:h.h+' '+h.title.slice(0,10),value:h.count,color:h.color})),'#8B5CF6')}
  264. </div>
  265. </section>
  266. <!-- Ch5: 地理分布 -->
  267. <section class="report-section" data-slide-num="05" data-tone="green">
  268. <div class="section-inner">
  269. <div class="section-head">
  270. <div class="section-eyebrow" style="color:var(--green);">CHAPTER 05</div>
  271. <h2 class="text-h1">用户地理分布</h2>
  272. <p class="section-sub">抖音评论IP定位 · TOP 9省份</p>
  273. </div>
  274. <div class="grid grid-2">
  275. <div>${barChart(geoData.map(g=>({label:g.label,value:g.value,color:'#00DC82'})),'#00DC82')}</div>
  276. <div class="card" style="border-left:3px solid var(--green);">
  277. <div style="font-weight:700;margin-bottom:16px;font-size:1.1rem;">关键发现</div>
  278. <div style="line-height:2;">
  279. <div style="margin-bottom:10px;"><span style="color:var(--green);font-weight:600;">● 广东(451条)</span> 最大评论来源省</div>
  280. <div style="margin-bottom:10px;"><span style="color:var(--green);font-weight:600;">● 江苏(394条)</span> 江浙沪经济发达区</div>
  281. <div style="margin-bottom:10px;"><span style="color:var(--green);font-weight:600;">● 山东(273条)</span> 北方第一人口大省</div>
  282. <div style="margin-top:16px;padding:10px 14px;background:#00DC8210;border-radius:6px;color:var(--text-2);font-size:0.85rem;">
  283. ⚠ 江西本省评论较少,需加强本地推广
  284. </div>
  285. </div>
  286. </div>
  287. </div>
  288. </div>
  289. </section>
  290. <!-- Ch6: 增长机会 -->
  291. <section class="report-section" data-slide-num="06" data-tone="orange">
  292. <div class="section-inner">
  293. <div class="section-head">
  294. <div class="section-eyebrow" style="color:var(--orange);">CHAPTER 06</div>
  295. <h2 class="text-h1">增长机会 × 新渠道</h2>
  296. <p class="section-sub">基于VOC发现的三大突破口</p>
  297. </div>
  298. <div class="grid grid-3">
  299. <div class="card" style="border-top:2px solid var(--green);">
  300. <div style="font-size:2.2rem;margin-bottom:14px;">🏥</div>
  301. <div style="font-weight:700;color:var(--green);font-size:1rem;margin-bottom:10px;">医院地推</div>
  302. <p class="text-sm" style="margin-bottom:14px;line-height:1.7;">直接触达即将生产的精准用户,转化率远高于泛流量</p>
  303. <div style="display:flex;gap:16px;margin-bottom:14px;">
  304. <div><div style="font-size:1.3rem;font-weight:700;color:var(--green);">50-100元</div><div class="text-xs">客资成本</div></div>
  305. <div><div style="font-size:1.3rem;font-weight:700;color:var(--green);">极高</div><div class="text-xs">转化率</div></div>
  306. </div>
  307. <div class="tag green">H1: ${(meta.hypotheses?.H1||0).toLocaleString()}条</div>
  308. </div>
  309. <div class="card" style="border-top:2px solid var(--amber);">
  310. <div style="font-size:2.2rem;margin-bottom:14px;">🎬</div>
  311. <div style="font-weight:700;color:var(--amber);font-size:1rem;margin-bottom:10px;">短剧营销</div>
  312. <p class="text-sm" style="margin-bottom:14px;line-height:1.7;">借南昌万亿短剧政策红利,低成本品牌曝光</p>
  313. <div style="display:flex;gap:16px;margin-bottom:14px;">
  314. <div><div style="font-size:1.3rem;font-weight:700;color:var(--amber);">3-5万</div><div class="text-xs">冠名成本</div></div>
  315. <div><div style="font-size:1.3rem;font-weight:700;color:var(--amber);">高</div><div class="text-xs">曝光性价比</div></div>
  316. </div>
  317. <div class="tag">H3: ${(meta.hypotheses?.H3||0).toLocaleString()}条</div>
  318. </div>
  319. <div class="card" style="border-top:2px solid var(--blue);">
  320. <div style="font-size:2.2rem;margin-bottom:14px;">📱</div>
  321. <div style="font-weight:700;color:var(--blue);font-size:1rem;margin-bottom:10px;">多渠道组合</div>
  322. <p class="text-sm" style="margin-bottom:14px;line-height:1.7;">抖音+小红书+美团+私域,老带新提升LTV</p>
  323. <div style="display:flex;gap:16px;margin-bottom:14px;">
  324. <div><div style="font-size:1.3rem;font-weight:700;color:var(--blue);">500元</div><div class="text-xs">客资ROI</div></div>
  325. <div><div style="font-size:1.3rem;font-weight:700;color:var(--blue);">3-5个</div><div class="text-xs">渠道组合</div></div>
  326. </div>
  327. <div class="tag blue">H7: ${(meta.hypotheses?.H7||0).toLocaleString()}条</div>
  328. </div>
  329. </div>
  330. </div>
  331. </section>
  332. <!-- Ch7: 4P配称蓝图 -->
  333. <section class="report-section" data-slide-num="07" data-tone="amber">
  334. <div class="section-inner">
  335. <div class="section-head">
  336. <div class="section-eyebrow">CHAPTER 07</div>
  337. <h2 class="text-h1">4P 配称蓝图</h2>
  338. <p class="section-sub">落地执行计划</p>
  339. </div>
  340. <div class="grid grid-3">
  341. <div class="card card-accent">
  342. <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
  343. <span style="width:28px;height:28px;border-radius:50%;background:var(--amber);color:#000;font-weight:700;font-size:0.8rem;display:flex;align-items:center;justify-content:center;">1</span>
  344. <span style="font-weight:700;">阶段一(1-3月)</span>
  345. </div>
  346. <div style="line-height:2;font-size:0.88rem;color:var(--text-2);">
  347. <div>• 搭建抖音/小红书账号矩阵</div>
  348. <div>• 与南昌3-5家核心医院产科建立合作</div>
  349. <div>• 美团/大众点评店铺优化</div>
  350. <div>• 建立星级月嫂体系</div>
  351. </div>
  352. </div>
  353. <div class="card" style="border-left:3px solid var(--orange);">
  354. <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
  355. <span style="width:28px;height:28px;border-radius:50%;background:var(--orange);color:#000;font-weight:700;font-size:0.8rem;display:flex;align-items:center;justify-content:center;">2</span>
  356. <span style="font-weight:700;">阶段二(3-6月)</span>
  357. </div>
  358. <div style="line-height:2;font-size:0.88rem;color:var(--text-2);">
  359. <div>• 短视频日常运营(3-5条/周)</div>
  360. <div>• 首部合作短剧(3-5万)</div>
  361. <div>• 微信私域搭建+老带新</div>
  362. <div>• 小红书投流测试</div>
  363. </div>
  364. </div>
  365. <div class="card" style="border-left:3px solid var(--green);">
  366. <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
  367. <span style="width:28px;height:28px;border-radius:50%;background:var(--green);color:#000;font-weight:700;font-size:0.8rem;display:flex;align-items:center;justify-content:center;">3</span>
  368. <span style="font-weight:700;">阶段三(6-12月)</span>
  369. </div>
  370. <div style="line-height:2;font-size:0.88rem;color:var(--text-2);">
  371. <div>• 多渠道付费投放规模化</div>
  372. <div>• 短剧系列化(3-5部)</div>
  373. <div>• 覆盖南昌80%+核心医院</div>
  374. <div>• 品牌定制短剧(20-50万)</div>
  375. </div>
  376. </div>
  377. </div>
  378. </div>
  379. </section>
  380. <!-- Ch8: VOC高赞精选 -->
  381. <section class="report-section" data-slide-num="08" data-tone="rose">
  382. <div class="section-inner">
  383. <div class="section-head">
  384. <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 08</div>
  385. <h2 class="text-h1">VOC 高赞精选</h2>
  386. <p class="section-sub">真实用户声音</p>
  387. </div>
  388. <div style="margin-bottom:28px;">
  389. <div class="section-eyebrow" style="color:var(--green);margin-bottom:14px;">▲ 正面评价(高赞)</div>
  390. <div class="grid grid-2">${positives.map(v=>vocCard(v)).join('')}</div>
  391. </div>
  392. <div>
  393. <div class="section-eyebrow" style="color:var(--text-3);margin-bottom:14px;">● 中性评价(高赞)</div>
  394. <div class="grid grid-2">${neutrals.map(v=>vocCard(v)).join('')}</div>
  395. </div>
  396. </div>
  397. </section>
  398. </main>
  399. <footer class="footer">
  400. <div>洪城到家 · VOC 深度洞察报告</div>
  401. <div style="margin-top:8px;">数据来源:抖音评论采集 · ${meta.comments.toLocaleString()} 条真实VOC · Generated by OpenClaw</div>
  402. <div style="margin-top:4px;">${new Date().toLocaleDateString('zh-CN')}</div>
  403. </footer>
  404. <script>
  405. const obs=new IntersectionObserver(s=>s.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in-view')}}},{threshold:0.3});
  406. document.querySelectorAll('section.report-section').forEach(s=>obs.observe(s));
  407. document.querySelectorAll('.card').forEach(c=>{c.addEventListener('mouseenter',()=>{document.body.style.setProperty('--amb-x',(Math.random()*30+10)+'%');document.body.style.setProperty('--amb-y',(Math.random()*30+10)+'%')})});
  408. </script>
  409. </body>
  410. </html>`;
  411. fs.writeFileSync(OUT_FILE, html, 'utf8');
  412. console.log('✅ 深色风格报告已生成: ' + OUT_FILE);
  413. console.log(' VOC数据: ' + meta.comments.toLocaleString() + ' 条');