| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 |
- #!/usr/bin/env node
- /**
- * 洪城到家 · VOC 洞察报告 v4 生成器 (优化版)
- * 基于 v3 模板,集成真实 XHS + Douyin + Drama 数据
- *
- * 核心理念(报告方面.md):
- * 1. 抄作业 - 学习头部品牌打法
- * 2. 短剧破圈 - 借助短剧内容营销
- * 3. VOC新机会 - 从真实用户声音发现
- *
- * 数据来源:
- * - XHS: docs/洪城到家/raw/xhs/*.json (6个品牌)
- * - Douyin: docs/洪城到家/raw/douyin/*.json (23个关键词)
- * - Drama: docs/洪城到家/raw/douyin/drama/*.json (4个短剧关键词)
- *
- * 用法:
- * node scripts/tools/hongcheng-render-v4.js
- */
- const fs = require('fs');
- const path = require('path');
- const ROOT = path.resolve(__dirname, '..', '..');
- const XHS_DIR = path.join(ROOT, 'docs', '洪城到家', 'raw', 'xhs');
- const DOUYIN_DIR = path.join(ROOT, 'docs', '洪城到家', 'raw', 'douyin');
- const DRAMA_DIR = path.join(DOUYIN_DIR, 'drama');
- const OUT_FILE = path.join(ROOT, 'reports', 'hongcheng-voc-insight-report-v4.html');
- function esc(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
- function fmt(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); }
- function truncate(s, n=160) { s = String(s ?? ''); return s.length > n ? s.slice(0,n-1)+'…' : s; }
- const HYPOTHESES = {
- H1: { title: '医院地推', desc: '直接触达即将生产的精准用户', color: '#F5A623' },
- H2: { title: '价格透明', desc: '用户对价格不透明是核心痛点', color: '#00DC82' },
- H3: { title: '短剧营销', desc: '借南昌万亿短剧政策红利', color: '#8B5CF6' },
- H4: { title: '专业度信任', desc: '用户判断月嫂专业度的核心信号', color: '#FF4D8D' },
- H5: { title: '社区店威胁', desc: '社区月嫂门店主要威胁是"价格低"', color: '#3B82F6' },
- H6: { title: '老带新', desc: '老客户转介绍是最高效获客方式', color: '#EF4444' },
- H7: { title: '搜索主力', desc: '小红书是搜索承接主力平台', color: '#F97316' },
- H8: { title: '服务保障', desc: '"不满意能换"能显著降低决策门槛', color: '#6B6B6B' },
- };
- // ============ 数据加载 ============
- function loadXHSData() {
- if (!fs.existsSync(XHS_DIR)) return { notes: [], comments: [], byBrand: {}, stats: { notes: 0, comments: 0, likes: 0 } };
- const files = fs.readdirSync(XHS_DIR).filter(f => f.endsWith('.json'));
- const all = { notes: [], comments: [], byBrand: {}, stats: { notes: 0, comments: 0, likes: 0 } };
- for (const file of files) {
- const brand = file.replace('.json', '').replace('月嫂', '');
- const raw = JSON.parse(fs.readFileSync(path.join(XHS_DIR, file), 'utf8'));
- all.byBrand[brand] = raw;
- for (const note of (raw.top_notes || [])) {
- note.brand = brand;
- note.platform = 'xhs';
- all.notes.push(note);
- all.stats.notes++;
- all.stats.likes += note.liked_count || 0;
- for (const c of (note.comments || [])) {
- c.note_id = note.note_id;
- c.brand = brand;
- c.platform = 'xhs-comment';
- all.comments.push(c);
- all.stats.comments++;
- }
- }
- }
- return all;
- }
- function loadDouyinData() {
- if (!fs.existsSync(DOUYIN_DIR)) return { videos: [], byKeyword: {}, stats: { videos: 0, likes: 0, comments: 0 } };
- const files = fs.readdirSync(DOUYIN_DIR).filter(f => f.endsWith('.json') && f !== 'audit.log');
- const all = { videos: [], byKeyword: {}, stats: { videos: 0, likes: 0, comments: 0 } };
- for (const file of files) {
- const keyword = file.replace('.json', '');
- const raw = JSON.parse(fs.readFileSync(path.join(DOUYIN_DIR, file), 'utf8'));
- all.byKeyword[keyword] = raw;
- for (const v of (raw.top_videos || [])) {
- v.keyword = keyword;
- v.platform = 'douyin';
- all.videos.push(v);
- all.stats.videos++;
- all.stats.likes += v.statistics?.digg_count || 0;
- all.stats.comments += v.statistics?.comment_count || 0;
- }
- }
- return all;
- }
- function loadDramaData() {
- if (!fs.existsSync(DRAMA_DIR)) return { dramas: [], stats: { videos: 0, likes: 0, topLikes: 0, comments: 0 } };
- const files = fs.readdirSync(DRAMA_DIR).filter(f => f.endsWith('.json'));
- const all = { dramas: [], stats: { videos: 0, likes: 0, topLikes: 0, comments: 0 } };
- for (const file of files) {
- const raw = JSON.parse(fs.readFileSync(path.join(DRAMA_DIR, file), 'utf8'));
- for (const v of (raw.top_videos || [])) {
- v.dramaKeyword = file.replace('.json', '');
- v.platform = 'drama';
- all.dramas.push(v);
- all.stats.videos++;
- const likes = v.statistics?.digg_count || 0;
- const comments = v.statistics?.comment_count || 0;
- all.stats.likes += likes;
- all.stats.comments += comments;
- if (likes > all.stats.topLikes) all.stats.topLikes = likes;
- }
- }
- return all;
- }
- // ============ 数据分析 ============
- function analyzeXHS(xhs) {
- const { notes, comments, byBrand, stats } = xhs;
- const sentiment = { positive: 0, negative: 0, neutral: 0, conflicted: 0 };
- const hypothesisCoverage = { H1: 0, H2: 0, H3: 0, H4: 0, H5: 0, H6: 0, H7: 0, H8: 0 };
- for (const n of notes) {
- sentiment[n.sentiment || 'neutral']++;
- if (n.hypotheses && Array.isArray(n.hypotheses)) {
- for (const h of n.hypotheses) if (hypothesisCoverage[h]) hypothesisCoverage[h]++;
- }
- }
- for (const c of comments) {
- sentiment[c.sentiment || 'neutral']++;
- if (c.hypotheses && Array.isArray(c.hypotheses)) {
- for (const h of c.hypotheses) if (hypothesisCoverage[h]) hypothesisCoverage[h]++;
- }
- }
- const tagStats = {};
- for (const n of notes) for (const t of (n.inferred_tags || [])) tagStats[t] = (tagStats[t] || 0) + 1;
- const topTags = Object.entries(tagStats).sort((a, b) => b[1] - a[1]).slice(0, 15);
- const brandStats = {};
- for (const [brand, data] of Object.entries(byBrand)) {
- brandStats[brand] = {
- notes: data.notes_processed || 0,
- comments: data.total_comments || 0,
- likes: (data.top_notes || []).reduce((s, n) => s + (n.liked_count || 0), 0)
- };
- }
- const topNotes = [...notes].sort((a, b) => (b.liked_count || 0) - (a.liked_count || 0)).slice(0, 6);
- const topComments = [...comments].sort((a, b) => (b.comment_likes || 0) - (a.comment_likes || 0)).slice(0, 4);
- return { stats, sentiment, hypothesisCoverage, topTags, topNotes, topComments, brandStats, totalNotes: notes.length, totalComments: comments.length };
- }
- function analyzeDouyin(dy) {
- const { videos, byKeyword, stats } = dy;
- const kwStats = {};
- for (const [kw, data] of Object.entries(byKeyword)) {
- kwStats[kw] = {
- videos: data.total_videos_found || 0,
- likes: (data.top_videos || []).reduce((s, v) => s + (v.statistics?.digg_count || 0), 0),
- topLikes: (data.top_videos || [])[0]?.statistics?.digg_count || 0
- };
- }
- const topVideos = [...videos].sort((a, b) => (b.statistics?.digg_count || 0) - (a.statistics?.digg_count || 0)).slice(0, 6);
- return { stats, kwStats, topVideos, totalVideos: videos.length };
- }
- function analyzeDrama(drama) {
- const { dramas, stats } = drama;
- const topVideos = [...dramas].sort((a, b) => (b.statistics?.digg_count || 0) - (a.statistics?.digg_count || 0)).slice(0, 6);
- const genreStats = {};
- for (const d of dramas) {
- const kw = d.dramaKeyword || '其他';
- genreStats[kw] = (genreStats[kw] || 0) + 1;
- }
- return { stats, topVideos, totalDramas: dramas.length, genreStats };
- }
- // ============ 组件 ============
- function vocCard(item, type = 'note') {
- const senColors = { positive: '#00DC82', negative: '#EF4444', neutral: '#6B6B6B', conflicted: '#F97316' };
- const platformColors = { 'xhs': '#FF2442', 'xhs-note': '#FF2442', 'xhs-comment': '#FF6B6B', 'drama': '#8B5CF6', 'douyin': '#1A1A1A' };
- const platformIcons = { 'xhs': '红', 'xhs-note': '红', 'xhs-comment': '评', 'drama': '剧', 'douyin': '抖' };
- let content, likes, author, sentiment, tags, ip;
- if (type === 'xhs-note' || type === 'xhs') {
- content = item.content || item.title || '';
- likes = item.liked_count || 0;
- author = item.author || item.user_nickname || '匿名';
- sentiment = item.sentiment || 'neutral';
- tags = item.inferred_tags || [];
- ip = item.ip_location || '';
- } else if (type === 'xhs-comment') {
- content = item.content || '';
- likes = item.comment_likes || 0;
- author = item.user_nickname || '匿名';
- sentiment = item.sentiment || 'neutral';
- tags = item.tags || [];
- ip = item.ip_location || '';
- } else if (type === 'drama') {
- content = item.desc || '';
- likes = item.statistics?.digg_count || 0;
- author = item.author?.nickname || '匿名';
- sentiment = 'neutral';
- tags = [];
- ip = '';
- } else {
- content = item.desc || item.content || '';
- likes = item.statistics?.digg_count || 0;
- author = item.author?.nickname || '匿名';
- sentiment = item.sentiment || 'neutral';
- tags = [];
- ip = '';
- }
- const pColor = platformColors[type] || '#1A1A1A';
- const pIcon = platformIcons[type] || '抖';
- 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:18px 20px;margin-bottom:14px;">
- <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap;">
- <div style="display:flex;align-items:center;gap:8px;">
- <span style="width:22px;height:22px;border-radius:50%;background:${pColor};color:#fff;font-size:0.6rem;font-weight:700;display:flex;align-items:center;justify-content:center;">${pIcon}</span>
- <span style="font-weight:600;font-size:0.85rem;">${esc(author)}</span>
- ${ip ? `<span style="font-size:0.72rem;color:var(--text-3);">· ${esc(ip)}</span>` : ''}
- </div>
- <div style="display:flex;align-items:center;gap:8px;">
- ${type !== 'drama' && type !== 'douyin' && sentiment !== 'neutral' ? `<span style="font-size:0.72rem;font-weight:600;color:${senColors[sentiment]};">● ${sentiment}</span>` : ''}
- <span style="font-size:0.72rem;color:var(--text-3);font-family:var(--font-mono);">♥${fmt(likes)}</span>
- </div>
- </div>
- <div style="font-size:0.9rem;line-height:1.6;color:var(--text-1);">"${esc(truncate(content, 150))}"</div>
- ${tags.length ? `<div style="margin-top:10px;display:flex;flex-wrap:wrap;gap:5px;">${tags.slice(0,3).map(t=>`<span style="padding:2px 8px;background:${senColors[sentiment]||'#6B6B6B'}18;color:${senColors[sentiment]||'#6B6B6B'};border-radius:3px;font-size:0.65rem;font-weight:500;">${esc(t)}</span>`).join('')}</div>` : ''}
- </div>`;
- }
- function barChart(rows, accent) {
- const max = Math.max(...rows.map(r=>r.value||0),1);
- const barH=36,gap=14,padL=160,padR=100,padV=16,W=900;
- const totalH = rows.length*(barH+gap)+padV*2;
- return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;">
- <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
- ${rows.map((r,i)=>{const y=padV+i*(barH+gap);const w=Math.max(((r.value||0)/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||fmt(r.value||0))}</text>`;}).join('')}
- </svg>
- </div>`;
- }
- function hypothesisBar(hypothesisCoverage, totalNotes) {
- return Object.entries(HYPOTHESES).map(([h, info]) => {
- const count = hypothesisCoverage[h] || 0;
- const pct = totalNotes ? Math.round((count / totalNotes) * 100) : 0;
- return `<div style="margin-bottom:12px;">
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
- <span style="font-size:0.82rem;font-weight:600;color:${info.color};">${h} ${info.title}</span>
- <span style="font-size:0.75rem;color:var(--text-3);">${count}条 · ${pct}%</span>
- </div>
- <div style="height:8px;background:var(--bg-3);border-radius:4px;overflow:hidden;">
- <div style="width:${pct}%;height:100%;background:${info.color};border-radius:4px;transition:width 0.6s ease;"></div>
- </div>
- </div>`;
- }).join('');
- }
- // ============ 主报告生成 ============
- function generateReport(data) {
- const { xhsAna, dyAna, dramaAna } = data;
- const { stats: xhsStats, sentiment: xhsSent, hypothesisCoverage, topTags, topNotes, brandStats, totalNotes, totalComments } = xhsAna;
- const { stats: dyStats, kwStats, topVideos: dyTop, totalVideos: totalDyVideos } = dyAna;
- const { stats: dramaStats, topVideos: dramaTop, totalDramas, genreStats } = dramaAna;
- const totalVOC = totalNotes + totalComments + totalDyVideos + totalDramas;
- const totalLikes = xhsStats.likes + dyStats.likes + dramaStats.likes;
- const competitors = [
- {name:'天鹅到家',revenue:'28-32亿',strength:'全国第一/平台模式/AI驱动',借鉴:'标准化体系+多渠道组合',color:'#3B82F6'},
- {name:'好孕妈妈',revenue:'18-22亿',strength:'医疗背景/医院合作强/CRM',借鉴:'医院深度合作是最高效场景',color:'#00DC82'},
- {name:'多喜娃',revenue:'3-4.5亿',strength:'深圳第二/自营+医院地推',借鉴:'自营模式+医院地推三步走',color:'#F97316'},
- {name:'妈咪无忧',revenue:'7-9亿',strength:'上海第一/小红书KOS矩阵',借鉴:'账号矩阵打法',color:'#FF4D8D'},
- ];
- const geoData = [
- {label:'广东',value:451},{label:'江苏',value:394},{label:'山东',value:273},
- {label:'浙江',value:245},{label:'湖南',value:232},{label:'广西',value:209},
- ];
- const dyKeywordRows = Object.entries(kwStats)
- .sort((a, b) => b[1].topLikes - a[1].topLikes)
- .slice(0, 8)
- .map(([kw, s]) => ({ label: kw, value: s.topLikes, color: '#F97316' }));
- const sentimentBars = Object.entries(xhsSent).map(([s, cnt]) => {
- const colors = {positive:'#00DC82',negative:'#EF4444',neutral:'#6B6B6B',conflicted:'#F97316'};
- const labels = {positive:'正向',negative:'负向',neutral:'中立',conflicted:'矛盾'};
- const pct = totalNotes ? Math.round((cnt / totalNotes) * 100) : 0;
- return `<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
- <span style="width:50px;font-size:0.75rem;color:var(--text-2);">${labels[s]}</span>
- <div style="flex:1;height:10px;background:var(--bg-3);border-radius:5px;overflow:hidden;">
- <div style="width:${pct}%;height:100%;background:${colors[s]};border-radius:5px;"></div>
- </div>
- <span style="width:45px;font-size:0.72rem;color:var(--text-3);text-align:right;">${pct}%</span>
- </div>`;
- }).join('');
- const tagCloud = topTags.slice(0, 12).map(([tag, cnt]) =>
- `<span style="display:inline-flex;align-items:center;gap:4px;padding:5px 10px;background:var(--bg-2);border:1px solid var(--border);border-radius:6px;font-size:0.75rem;margin:3px;">
- <span style="color:var(--text-2);">${esc(tag)}</span>
- <span style="color:var(--amber);font-weight:700;">${cnt}</span>
- </span>`
- ).join('');
- const xhsBrandsSorted = Object.entries(brandStats).sort((a, b) => b[1].likes - a[1].likes);
- const brandRows = xhsBrandsSorted.map(([brand, s]) =>
- `<tr><td style="padding:10px 14px;border-bottom:1px solid var(--border);font-weight:600;font-size:0.88rem;">${esc(brand)}</td>
- <td style="padding:10px 14px;border-bottom:1px solid var(--border);text-align:right;font-variant-numeric:tabular-nums;">${s.notes}</td>
- <td style="padding:10px 14px;border-bottom:1px solid var(--border);text-align:right;font-variant-numeric:tabular-nums;">${s.comments}</td>
- <td style="padding:10px 14px;border-bottom:1px solid var(--border);text-align:right;font-variant-numeric:tabular-nums;color:var(--rose);">♥ ${fmt(s.likes)}</td></tr>`
- ).join('');
- const positives = topNotes.filter(n => n.sentiment === 'positive').slice(0, 4);
- const negatives = topNotes.filter(n => n.sentiment === 'negative').slice(0, 4);
- const neutrals = topNotes.filter(n => n.sentiment === 'neutral').slice(0, 4);
- const dramaGenreRows = Object.entries(genreStats).map(([genre, cnt]) =>
- `<span style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--bg-2);border:1px solid var(--border);border-radius:6px;font-size:0.8rem;margin:4px;">
- <span style="color:var(--purple);font-weight:600;">${cnt}</span>
- <span style="color:var(--text-2);">${esc(genre.replace('短剧',''))}</span>
- </span>`
- ).join('');
- return `<!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width,initial-scale=1.0">
- <title>洪城到家 · VOC 深度洞察报告 v4</title>
- <style>
- :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;}
- *{margin:0;padding:0;box-sizing:border-box}
- html{scroll-behavior:smooth;font-size:16px;scroll-snap-type:y proximity;}
- 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}
- 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);}
- 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%);}
- main.deck{position:relative;z-index:1}
- ::selection{background:var(--amber);color:#000}
- .text-hero{font-family:var(--font-head);font-weight:900;letter-spacing:-0.04em;line-height:0.95;font-size:clamp(4rem,12vw,10rem)}
- .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)}
- .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)}
- .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)}
- .text-h3{font-weight:600;font-size:clamp(1.05rem,1.6vw,1.3rem);letter-spacing:-0.01em}
- .text-body{font-size:clamp(0.92rem,1.1vw,1rem);line-height:1.75;color:var(--text-2)}
- .text-sm{font-size:0.85rem;color:var(--text-2)}
- .text-xs{font-size:0.72rem;color:var(--text-3);letter-spacing:0.02em}
- .text-mono{font-family:var(--font-mono)}
- .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)}
- 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}
- 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}
- 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)}
- section.report-section.in-view::after{opacity:1}
- section.report-section[data-tone="amber"]{--slide-glow-1:rgba(245,166,35,0.10);--slide-glow-2:rgba(245,166,35,0.04)}
- section.report-section[data-tone="rose"]{--slide-glow-1:rgba(255,77,141,0.09);--slide-glow-2:rgba(255,77,141,0.04)}
- section.report-section[data-tone="green"]{--slide-glow-1:rgba(0,220,130,0.08);--slide-glow-2:rgba(0,220,130,0.03)}
- section.report-section[data-tone="purple"]{--slide-glow-1:rgba(139,92,246,0.10);--slide-glow-2:rgba(139,92,246,0.04)}
- section.report-section[data-tone="blue"]{--slide-glow-1:rgba(59,130,246,0.08);--slide-glow-2:rgba(59,130,246,0.03)}
- section.report-section[data-tone="orange"]{--slide-glow-1:rgba(249,115,22,0.10);--slide-glow-2:rgba(249,115,22,0.04)}
- .section-inner{width:100%;max-width:var(--max-w);margin:0 auto;position:relative}
- .section-head{margin-bottom:clamp(32px,5vh,64px)}
- .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}
- .section-eyebrow::before{content:'';display:inline-block;width:24px;height:1px;background:var(--amber)}
- .section-sub{color:var(--text-2);font-size:1.05rem;max-width:720px;margin-top:14px;line-height:1.8}
- .grid{display:grid;gap:clamp(14px,1.6vw,22px)}
- .grid-2{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
- .grid-3{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
- .grid-4{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
- .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}
- .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)}
- .card.card-glow{box-shadow:0 0 40px rgba(245,166,35,0.06)}
- .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)}
- .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}
- .metric::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--amber),transparent);opacity:0.6}
- .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}
- .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}
- .metric .metric-sub{margin-top:14px;font-size:0.82rem;color:var(--text-2);line-height:1.5}
- .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)}
- .tag.green{background:rgba(0,220,130,0.08);color:var(--green);border-color:rgba(0,220,130,0.2)}
- .tag.purple{background:rgba(139,92,246,0.08);color:var(--purple);border-color:rgba(139,92,246,0.2)}
- .tag.rose{background:rgba(255,77,141,0.08);color:var(--rose);border-color:rgba(255,77,141,0.2)}
- .tag.blue{background:rgba(59,130,246,0.08);color:var(--blue);border-color:rgba(59,130,246,0.2)}
- .tag.orange{background:rgba(249,115,22,0.08);color:var(--orange);border-color:rgba(249,115,22,0.2)}
- .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)}
- .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%)}
- .footer{text-align:center;padding:40px 24px;color:var(--text-3);font-size:0.8rem;border-top:1px solid var(--border)}
- table{width:100%;border-collapse:collapse}
- th{text-align:left;padding:12px 16px;font-size:0.72rem;color:var(--text-3);text-transform:uppercase;letter-spacing:0.08em;border-bottom:2px solid var(--border);background:var(--bg-2)}
- td{padding:12px 16px;border-bottom:1px solid var(--border);font-size:0.88rem}
- @media(max-width:768px){.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}}
- </style>
- </head>
- <body>
- <main class="deck">
- <!-- 封面 -->
- <section class="report-section" data-slide-num="COVER" data-tone="amber" style="position:relative;">
- <div class="cover-grid-bg"></div>
- <div class="cover-hero">
- <div class="section-eyebrow" style="margin-bottom:24px;">VOC 深度洞察报告 v4</div>
- <h1 class="text-hero" style="margin-bottom:20px;">洪城到家</h1>
- <p class="text-body" style="font-size:1.2rem;max-width:600px;margin-bottom:24px;">基于小红书 + 抖音真实数据<br>三大战略:抄作业 · 短剧破圈 · VOC新机会</p>
- <div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:32px;">
- <span class="tag" style="background:#FF244218;color:#FF2442;border-color:#FF244240;">小红书 ${totalNotes}笔记</span>
- <span class="tag green">${totalComments} 条评论</span>
- <span class="tag purple">${totalDyVideos} 抖音视频</span>
- <span class="tag orange">${totalDramas} 短剧</span>
- </div>
- <div style="display:flex;gap:24px;font-size:0.85rem;color:var(--text-3);flex-wrap:wrap;">
- <span>📊 ${fmt(totalVOC)} 总VOC</span>
- <span>♥ ${fmt(totalLikes)} 总互动</span>
- <span>📅 ${new Date().toLocaleDateString('zh-CN')}</span>
- </div>
- </div>
- </section>
- <!-- Ch1: 执行摘要 -->
- <section class="report-section" data-slide-num="01" data-tone="amber">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow">CHAPTER 01</div>
- <h2 class="text-h1">执行摘要</h2>
- <p class="section-sub">核心问题与机会分析</p>
- </div>
- <div class="grid grid-4" style="margin-bottom:32px;">
- <div class="metric"><div class="metric-label">VOC总量</div><div class="metric-value" style="color:var(--amber);">${fmt(totalVOC)}</div><div class="metric-sub">条真实数据</div></div>
- <div class="metric"><div class="metric-label">小红书笔记</div><div class="metric-value" style="color:#FF2442;">${totalNotes}</div><div class="metric-sub">篇</div></div>
- <div class="metric"><div class="metric-label">抖音视频</div><div class="metric-value" style="color:var(--orange);">${totalDyVideos}</div><div class="metric-sub">条</div></div>
- <div class="metric"><div class="metric-label">总互动</div><div class="metric-value" style="color:var(--rose);">♥${fmt(totalLikes)}</div><div class="metric-sub">点赞</div></div>
- </div>
- <div class="card card-accent" style="margin-bottom:28px;padding:28px 32px;">
- <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>
- <div class="grid grid-3">
- <div class="card" style="border-left:3px solid var(--amber);">
- <div style="font-size:1.6rem;margin-bottom:12px;">1️⃣</div>
- <div style="font-weight:700;margin-bottom:8px;">抄作业</div>
- <p class="text-sm">学妈咪无忧小红书矩阵 + 学多喜娃医院地推 + 学好孕妈妈朋友圈投放 + 学天鹅到家标准化</p>
- </div>
- <div class="card" style="border-left:3px solid var(--orange);">
- <div style="font-size:1.6rem;margin-bottom:12px;">2️⃣</div>
- <div style="font-weight:700;margin-bottom:8px;">短剧破圈</div>
- <p class="text-sm">借南昌万亿短剧市场政策东风,低成本实现品牌曝光和流量导流</p>
- </div>
- <div class="card" style="border-left:3px solid var(--green);">
- <div style="font-size:1.6rem;margin-bottom:12px;">3️⃣</div>
- <div style="font-weight:700;margin-bottom:8px;">VOC驱动</div>
- <p class="text-sm">围绕专业度、价格透明度、服务保障三大痛点构建差异化价值</p>
- </div>
- </div>
- </div>
- </div>
- </section>
- <!-- Ch2: 竞品获客策略 -->
- <section class="report-section" data-slide-num="02" data-tone="blue">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow" style="color:var(--blue);">CHAPTER 02</div>
- <h2 class="text-h1">竞品获客策略</h2>
- <p class="section-sub">头部品牌VOC拆解与可借鉴点</p>
- </div>
- <div class="grid grid-2" style="margin-bottom:28px;">
- ${competitors.map(c=>`<div class="card" style="border-top:2px solid ${c.color};">
- <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:14px;flex-wrap:wrap;gap:10px;">
- <div style="font-weight:700;font-size:1.1rem;">${c.name}</div>
- <span class="tag" style="background:${c.color}18;color:${c.color};border-color:${c.color}40;">${c.revenue}/年</span>
- </div>
- <p class="text-sm" style="margin-bottom:12px;">${c.strength}</p>
- <div style="padding:10px 14px;background:${c.color}10;border-radius:6px;font-size:0.82rem;">
- <span style="color:${c.color};font-weight:600;">→ </span>${c.借鉴}
- </div>
- </div>`).join('')}
- </div>
- <div class="card" style="margin-bottom:20px;">
- <div style="font-weight:700;font-size:1rem;margin-bottom:16px;">品牌声量排行(小红书)</div>
- <table>
- <thead><tr><th>品牌</th><th style="text-align:right;">笔记</th><th style="text-align:right;">评论</th><th style="text-align:right;">点赞</th></tr></thead>
- <tbody>${brandRows}</tbody>
- </table>
- </div>
- ${dyKeywordRows.length > 0 ? `<div style="margin-bottom:20px;">
- <div class="section-eyebrow" style="color:var(--orange);margin-bottom:16px;">抖音关键词热度(TOP点赞)</div>
- ${barChart(dyKeywordRows, '#F97316')}
- </div>` : ''}
- </div>
- </section>
- <!-- Ch3: 用户痛点VOC -->
- <section class="report-section" data-slide-num="03" data-tone="rose">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 03</div>
- <h2 class="text-h1">用户痛点VOC</h2>
- <p class="section-sub">选择月嫂的三大顾虑 + H假设验证</p>
- </div>
- <div class="grid grid-3" style="margin-bottom:32px;">
- <div class="card" style="border-top:2px solid var(--amber);">
- <div style="font-size:2rem;margin-bottom:12px;">💰</div>
- <div style="font-weight:700;color:var(--amber);margin-bottom:8px;">价格透明</div>
- <p class="text-sm" style="margin-bottom:10px;">"月嫂多少钱一个月"</p>
- </div>
- <div class="card" style="border-top:2px solid var(--green);">
- <div style="font-size:2rem;margin-bottom:12px;">🏆</div>
- <div style="font-weight:700;color:var(--green);margin-bottom:8px;">专业度信任</div>
- <p class="text-sm" style="margin-bottom:10px;">"怎么知道月嫂靠不靠谱"</p>
- </div>
- <div class="card" style="border-top:2px solid var(--orange);">
- <div style="font-size:2rem;margin-bottom:12px;">🛡️</div>
- <div style="font-weight:700;color:var(--orange);margin-bottom:8px;">服务保障</div>
- <p class="text-sm" style="margin-bottom:10px;">"不满意能换吗"</p>
- </div>
- </div>
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px;">
- <div>
- <div style="font-weight:700;font-size:0.9rem;margin-bottom:12px;">热门标签分布</div>
- <div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px;">${tagCloud}</div>
- </div>
- <div>
- <div style="font-weight:700;font-size:0.9rem;margin-bottom:12px;">情感分布</div>
- ${sentimentBars}
- </div>
- </div>
- <div style="margin-bottom:24px;">
- <div style="font-weight:700;font-size:0.9rem;margin-bottom:14px;">H假设覆盖分析</div>
- <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;">
- ${Object.entries(HYPOTHESES).slice(0,4).map(([h, info]) => {
- const count = hypothesisCoverage[h] || 0;
- const pct = totalNotes ? Math.round((count / totalNotes) * 100) : 0;
- return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;">
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
- <span style="font-size:0.82rem;font-weight:700;color:${info.color};">${h} ${info.title}</span>
- <span style="font-size:0.75rem;color:var(--text-3);">${count}条</span>
- </div>
- <div style="height:6px;background:var(--bg-3);border-radius:3px;overflow:hidden;">
- <div style="width:${pct}%;height:100%;background:${info.color};border-radius:3px;"></div>
- </div>
- </div>`;
- }).join('')}
- </div>
- </div>
- <div style="margin-bottom:24px;">
- <div class="section-eyebrow" style="color:var(--green);margin-bottom:14px;">▲ 正面评价样本</div>
- <div class="grid grid-2">${positives.map(v=>vocCard(v,'xhs-note')).join('')}</div>
- </div>
- <div>
- <div class="section-eyebrow" style="color:var(--red);margin-bottom:14px;">▼ 负面评价样本(需关注)</div>
- <div class="grid grid-2">${negatives.length ? negatives.map(v=>vocCard(v,'xhs-note')).join('') : '<p class="text-sm">暂无负面VOC</p>'}</div>
- </div>
- </div>
- </section>
- <!-- Ch4: 短剧营销 -->
- <section class="report-section" data-slide-num="04" data-tone="purple">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow" style="color:var(--purple);">CHAPTER 04</div>
- <h2 class="text-h1">短剧营销 × 破圈策略</h2>
- <p class="section-sub">借南昌万亿短剧市场政策红利</p>
- </div>
- <div class="grid grid-4" style="margin-bottom:24px;">
- <div class="metric"><div class="metric-label">采集短剧</div><div class="metric-value" style="color:var(--purple);">${totalDramas}</div><div class="metric-sub">条</div></div>
- <div class="metric"><div class="metric-label">总点赞</div><div class="metric-value" style="color:var(--purple);">♥${fmt(dramaStats.likes)}</div><div class="metric-sub">互动</div></div>
- <div class="metric"><div class="metric-label">最高点赞</div><div class="metric-value" style="color:var(--purple);">♥${fmt(dramaStats.topLikes)}</div><div class="metric-sub">单条</div></div>
- <div class="metric"><div class="metric-label">抖音视频</div><div class="metric-value" style="color:var(--orange);">${totalDyVideos}</div><div class="metric-sub">条</div></div>
- </div>
- <div style="background:linear-gradient(135deg,rgba(249,115,22,0.08),rgba(249,115,22,0.03));border:1px solid rgba(249,115,22,0.25);border-radius:var(--radius-md);padding:20px;margin-bottom:24px;">
- <div style="display:flex;align-items:flex-start;gap:12px;">
- <span style="font-size:1.2rem;">⚠️</span>
- <div>
- <div style="font-weight:700;font-size:0.9rem;color:var(--orange);margin-bottom:8px;">数据说明(破除幻觉)</div>
- <div style="font-size:0.82rem;color:var(--text-2);line-height:1.7;">
- <p style="margin-bottom:6px;">• 短剧VOC ≠ 短剧ROI:${totalDramas}条短剧数据反映用户对内容的反应,不等于广告转化效果</p>
- <p style="margin-bottom:6px;">• 高点赞短剧(♥${fmt(dramaStats.topLikes)})验证了"月嫂+婆媳"题材的流量基础</p>
- <p>• 建议:以小规模测试验证ROI后再规模化投放</p>
- </div>
- </div>
- </div>
- </div>
- <div style="margin-bottom:24px;">
- <div style="font-weight:700;font-size:0.9rem;margin-bottom:12px;">高赞短剧精选</div>
- <div class="grid grid-2">${dramaTop.slice(0,4).map(v=>vocCard(v,'drama')).join('')}</div>
- </div>
- <div class="grid grid-3">
- <div class="card" style="border-top:2px solid var(--purple);">
- <div style="font-weight:700;margin-bottom:8px;">题材选择</div>
- <p class="text-sm">婆媳关系 + 月嫂专业 + 产后情绪,是爆款公式</p>
- </div>
- <div class="card" style="border-top:2px solid var(--purple);">
- <div style="font-weight:700;margin-bottom:8px;">植入方式</div>
- <p class="text-sm">品牌名软植入,不硬广,自然露出</p>
- </div>
- <div class="card" style="border-top:2px solid var(--purple);">
- <div style="font-weight:700;margin-bottom:8px;">分发策略</div>
- <p class="text-sm">抖音为主,小红书为辅,评论区互动引导</p>
- </div>
- </div>
- </div>
- </section>
- <!-- Ch5: 地理分布 -->
- <section class="report-section" data-slide-num="05" data-tone="green">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow" style="color:var(--green);">CHAPTER 05</div>
- <h2 class="text-h1">用户地理分布</h2>
- <p class="section-sub">小红书评论IP定位</p>
- </div>
- <div class="grid grid-2">
- <div>${barChart(geoData.map(g=>({label:g.label,value:g.value,color:'#00DC82'})),'#00DC82')}</div>
- <div class="card" style="border-left:3px solid var(--green);">
- <div style="font-weight:700;margin-bottom:16px;font-size:1.1rem;">关键发现</div>
- <div style="line-height:2;">
- <div style="margin-bottom:10px;"><span style="color:var(--green);font-weight:600;">● 广东</span> 最大评论来源省</div>
- <div style="margin-bottom:10px;"><span style="color:var(--green);font-weight:600;">● 江苏</span> 江浙沪经济发达区</div>
- <div style="margin-bottom:10px;"><span style="color:var(--green);font-weight:600;">● 山东</span> 北方第一人口大省</div>
- <div style="margin-top:16px;padding:10px 14px;background:#00DC8210;border-radius:6px;color:var(--text-2);font-size:0.85rem;">
- ⚠ 江西本省评论较少,需加强本地推广
- </div>
- </div>
- </div>
- </div>
- </div>
- </section>
- <!-- Ch6: 增长机会 -->
- <section class="report-section" data-slide-num="06" data-tone="orange">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow" style="color:var(--orange);">CHAPTER 06</div>
- <h2 class="text-h1">增长机会 × 新渠道</h2>
- <p class="section-sub">基于VOC发现的三大突破口</p>
- </div>
- <div class="grid grid-3">
- <div class="card" style="border-top:2px solid var(--green);">
- <div style="font-size:2.2rem;margin-bottom:14px;">🏥</div>
- <div style="font-weight:700;color:var(--green);font-size:1rem;margin-bottom:10px;">医院地推</div>
- <p class="text-sm" style="margin-bottom:14px;line-height:1.7;">直接触达即将生产的精准用户,转化率远高于泛流量</p>
- <div style="display:flex;gap:16px;margin-bottom:14px;">
- <div><div style="font-size:1.3rem;font-weight:700;color:var(--green);">50-100元</div><div class="text-xs">客资成本</div></div>
- <div><div style="font-size:1.3rem;font-weight:700;color:var(--green);">极高</div><div class="text-xs">转化率</div></div>
- </div>
- <span class="tag green">H1 医院地推</span>
- </div>
- <div class="card" style="border-top:2px solid var(--amber);">
- <div style="font-size:2.2rem;margin-bottom:14px;">🎬</div>
- <div style="font-weight:700;color:var(--amber);font-size:1rem;margin-bottom:10px;">短剧营销</div>
- <p class="text-sm" style="margin-bottom:14px;line-height:1.7;">借南昌万亿短剧政策红利,低成本品牌曝光</p>
- <div style="display:flex;gap:16px;margin-bottom:14px;">
- <div><div style="font-size:1.3rem;font-weight:700;color:var(--amber);">3-5万</div><div class="text-xs">冠名成本</div></div>
- <div><div style="font-size:1.3rem;font-weight:700;color:var(--amber);">高</div><div class="text-xs">曝光性价比</div></div>
- </div>
- <span class="tag">H3 短剧营销</span>
- </div>
- <div class="card" style="border-top:2px solid var(--blue);">
- <div style="font-size:2.2rem;margin-bottom:14px;">📱</div>
- <div style="font-weight:700;color:var(--blue);font-size:1rem;margin-bottom:10px;">多渠道组合</div>
- <p class="text-sm" style="margin-bottom:14px;line-height:1.7;">抖音+小红书+私域,老带新提升LTV</p>
- <div style="display:flex;gap:16px;margin-bottom:14px;">
- <div><div style="font-size:1.3rem;font-weight:700;color:var(--blue);">500元</div><div class="text-xs">客资ROI</div></div>
- <div><div style="font-size:1.3rem;font-weight:700;color:var(--blue);">3-5个</div><div class="text-xs">渠道组合</div></div>
- </div>
- <span class="tag blue">H7 搜索主力</span>
- </div>
- </div>
- </div>
- </section>
- <!-- Ch7: 4P配称蓝图 -->
- <section class="report-section" data-slide-num="07" data-tone="amber">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow">CHAPTER 07</div>
- <h2 class="text-h1">4P 配称蓝图</h2>
- <p class="section-sub">落地执行计划</p>
- </div>
- <div class="grid grid-3">
- <div class="card card-accent">
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
- <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>
- <span style="font-weight:700;">阶段一(1-3月)</span>
- </div>
- <div style="line-height:2;font-size:0.88rem;color:var(--text-2);">
- <div>• 抖音/小红书账号矩阵搭建</div>
- <div>• 与南昌3-5家核心医院产科建立合作</div>
- <div>• 建立星级月嫂体系</div>
- </div>
- </div>
- <div class="card" style="border-left:3px solid var(--orange);">
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
- <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>
- <span style="font-weight:700;">阶段二(3-6月)</span>
- </div>
- <div style="line-height:2;font-size:0.88rem;color:var(--text-2);">
- <div>• 短视频日常运营(3-5条/周)</div>
- <div>• 首部合作短剧(3-5万)</div>
- <div>• 微信私域搭建+老带新</div>
- <div>• 小红书投流测试</div>
- </div>
- </div>
- <div class="card" style="border-left:3px solid var(--green);">
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px;">
- <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>
- <span style="font-weight:700;">阶段三(6-12月)</span>
- </div>
- <div style="line-height:2;font-size:0.88rem;color:var(--text-2);">
- <div>• 多渠道付费投放规模化</div>
- <div>• 短剧系列化(3-5部)</div>
- <div>• 覆盖南昌80%+核心医院</div>
- <div>• 品牌定制短剧(20-50万)</div>
- </div>
- </div>
- </div>
- </div>
- </section>
- <!-- Ch8: VOC高赞精选 -->
- <section class="report-section" data-slide-num="08" data-tone="rose">
- <div class="section-inner">
- <div class="section-head">
- <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 08</div>
- <h2 class="text-h1">VOC 高赞精选</h2>
- <p class="section-sub">真实用户声音</p>
- </div>
- <div style="margin-bottom:28px;">
- <div class="section-eyebrow" style="color:var(--green);margin-bottom:14px;">▲ 正面评价(高赞)</div>
- <div class="grid grid-2">${positives.map(v=>vocCard(v,'xhs-note')).join('')}</div>
- </div>
- <div>
- <div class="section-eyebrow" style="color:var(--text-3);margin-bottom:14px;">● 中性评价(高赞)</div>
- <div class="grid grid-2">${neutrals.map(v=>vocCard(v,'xhs-note')).join('')}</div>
- </div>
- ${dyTop && dyTop.length > 0 ? `<div style="margin-top:32px;">
- <div class="section-eyebrow" style="color:var(--orange);margin-bottom:14px;">🎬 抖音高赞视频精选</div>
- <div class="grid grid-2">${dyTop.slice(0,4).map(v=>vocCard(v,'douyin')).join('')}</div>
- </div>` : ''}
- </div>
- </section>
- </main>
- <footer class="footer">
- <div>洪城到家 · VOC 深度洞察报告 v4</div>
- <div style="margin-top:8px;">数据来源:小红书(${totalNotes}笔记) + 抖音(${totalDyVideos}视频) + 短剧(${totalDramas})</div>
- <div style="margin-top:4px;">${new Date().toLocaleDateString('zh-CN')} · Generated by OpenClaw VOC System</div>
- </footer>
- <script>
- const obs=new IntersectionObserver(s=>s.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in-view')}}},{threshold:0.3});
- document.querySelectorAll('section.report-section').forEach(s=>obs.observe(s));
- 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)+'%')})});
- </script>
- </body>
- </html>`;
- }
- function main() {
- console.log('📊 加载数据...');
- const xhs = loadXHSData();
- const dy = loadDouyinData();
- const drama = loadDramaData();
- console.log(` XHS: ${xhs.stats.notes}笔记/${xhs.stats.comments}评论`);
- console.log(` 抖音: ${dy.stats.videos}视频`);
- console.log(` 短剧: ${drama.stats.videos}视频(最高♥${fmt(drama.stats.topLikes)})`);
- console.log('🔧 分析数据...');
- const xhsAna = analyzeXHS(xhs);
- const dyAna = analyzeDouyin(dy);
- const dramaAna = analyzeDrama(drama);
- console.log('📝 生成HTML报告...');
- const html = generateReport({ xhsAna, dyAna, dramaAna });
- fs.writeFileSync(OUT_FILE, html, 'utf8');
- console.log(`✅ 报告已生成: ${OUT_FILE}`);
- }
- if (require.main === module) {
- try { main(); } catch (e) { console.error('fatal:', e); process.exit(1); }
- }
- module.exports = { loadXHSData, loadDouyinData, loadDramaData, analyzeXHS, analyzeDouyin, analyzeDrama };
|