ks-render-v2.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const KA = require('./ks-analyze.js');
  5. const ROOT = path.resolve(__dirname, '..', '..');
  6. const OUT_DIR = path.join(ROOT, 'reports');
  7. const OUT_FILE = path.join(OUT_DIR, 'ks-voc-insight-report.html');
  8. const data = KA.loadRawFiles();
  9. const meta = KA.getMeta(data);
  10. const topLiked = KA.getTopLiked(data, 30);
  11. const byHypothesis = KA.getByHypothesis(data);
  12. const byKeyword = KA.getTopByKeyword(data, 8);
  13. const highRiskVOC = KA.getHighRiskVOC(data, 15);
  14. function esc(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
  15. 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); }
  16. function truncate(s, n=200) { s = String(s ?? ''); return s.length > n ? s.slice(0,n-1)+'…' : s; }
  17. const hypos = [
  18. {h:'H1',title:'快聘是蓝领求职主阵地',count:byHypothesis.H1?.length||0,color:'#F5A623'},
  19. {h:'H3',title:'薪资透明度是核心关切',count:byHypothesis.H3?.length||0,color:'#00DC82'},
  20. {h:'H4',title:'食宿条件影响求职决策',count:byHypothesis.H4?.length||0,color:'#8B5CF6'},
  21. {h:'H5',title:'入职流程便捷性',count:byHypothesis.H5?.length||0,color:'#F97316'},
  22. {h:'H6',title:'求职被骗是普遍焦虑',count:byHypothesis.H6?.length||0,color:'#FF4D8D'},
  23. {h:'H7',title:'黑中介是核心痛点',count:byHypothesis.H7?.length||0,color:'#EF4444'},
  24. {h:'H8',title:'工资结算方式差异',count:byHypothesis.H8?.length||0,color:'#6B6B6B'},
  25. ].filter(h => h.count > 0);
  26. function vocCard(item, showKeyword = false) {
  27. const ip = item.ip_location ? ` [${esc(item.ip_location)}]` : '';
  28. const likes = item.likes || 0;
  29. 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;backdrop-filter:blur(8px);margin-bottom:12px;">
  30. <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap;">
  31. <div style="display:flex;align-items:center;gap:8px;">
  32. <span style="width:20px;height:20px;border-radius:50%;background:#FF2442;color:#fff;font-size:0.55rem;font-weight:700;display:flex;align-items:center;justify-content:center;">快</span>
  33. <span style="font-weight:600;font-size:0.82rem;">${esc(item.nickname||'匿名用户')}</span>
  34. <span style="font-size:0.7rem;color:var(--text-3);">${ip}</span>
  35. </div>
  36. <div style="display:flex;align-items:center;gap:8px;">
  37. <span style="font-size:0.72rem;color:var(--text-3);font-family:var(--font-mono);">♥${fmtLikes(likes)}</span>
  38. ${showKeyword && item.keyword ? `<span class="tag" style="font-size:0.6rem;padding:2px 6px;">${esc(item.keyword)}</span>` : ''}
  39. </div>
  40. </div>
  41. <div style="font-size:0.88rem;line-height:1.6;color:var(--text-1);">"${esc(truncate(item.content,180))}"</div>
  42. </div>`;
  43. }
  44. function insightBanner(num, title, body, stat, statLabel, color) {
  45. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(24px,3vw,40px);position:relative;overflow:hidden;min-height:200px;display:flex;flex-direction:column;justify-content:space-between;">
  46. <div style="position:absolute;top:12px;right:20px;font-family:var(--font-head);font-weight:900;font-size:clamp(3rem,6vw,5rem);color:var(--bg-3);pointer-events:none;">${num}</div>
  47. <h3 style="font-family:var(--font-head);font-weight:800;font-size:clamp(1.2rem,2vw,1.8rem);letter-spacing:-0.02em;max-width:85%;position:relative;z-index:1;">${title}</h3>
  48. <p style="color:var(--text-2);font-size:0.95rem;line-height:1.7;max-width:85%;position:relative;z-index:1;">${body}</p>
  49. <div style="display:flex;gap:20px;margin-top:16px;padding-top:16px;border-top:1px solid var(--border);position:relative;z-index:1;">
  50. <div><span style="font-size:1.4rem;font-weight:800;color:${color};font-family:var(--font-head);">${stat}</span><span style="font-size:0.72rem;color:var(--text-3);margin-left:6px;">${statLabel}</span></div>
  51. </div>
  52. </div>`;
  53. }
  54. function barChart(rows, accent) {
  55. const max = Math.max(...rows.map(r=>r.value),1);
  56. const barH=32,gap=12,padL=120,padR=80,padV=12,W=900;
  57. const totalH = rows.length*(barH+gap)+padV*2;
  58. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:16px 20px;">
  59. <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
  60. ${rows.map((r,i)=>{const y=padV+i*(barH+gap);const w=Math.max((r.value/max)*(W-padL-padR),4);return `<text x="${padL-8}" y="${y+barH/2+4}" text-anchor="end" font-size="12" fill="var(--text-2)">${esc(r.label)}</text><rect x="${padL}" y="${y}" width="${w}" height="${barH}" rx="3" fill="${r.color||accent}" opacity="0.85"/><text x="${padL+w+8}" y="${y+barH/2+4}" font-size="12" font-weight="700" fill="${r.color||accent}" font-family="monospace">${esc(r.valueLabel||r.value)}</text>`;}).join('')}
  61. </svg>
  62. </div>`;
  63. }
  64. const html = `<!DOCTYPE html>
  65. <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
  66. <title>快手快聘 · 蓝领求职洞察报告</title>
  67. <style>
  68. :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;}
  69. *{margin:0;padding:0;box-sizing:border-box}
  70. html{scroll-behavior:smooth;font-size:16px;scroll-snap-type:y proximity;}
  71. 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}
  72. 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);}
  73. 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%);}
  74. main.deck{position:relative;z-index:1}
  75. ::selection{background:var(--amber);color:#000}
  76. .text-hero{font-family:var(--font-head);font-weight:900;letter-spacing:-0.04em;line-height:0.95;font-size:clamp(3.5rem,10vw,8rem)}
  77. .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)}
  78. .text-h1{font-family:var(--font-head);font-weight:700;letter-spacing:-0.02em;line-height:1.1;font-size:clamp(1.8rem,3.5vw,3rem)}
  79. .text-h2{font-family:var(--font-head);font-weight:700;letter-spacing:-0.015em;line-height:1.2;font-size:clamp(1.3rem,2.2vw,1.8rem)}
  80. .text-h3{font-weight:600;font-size:clamp(1rem,1.5vw,1.2rem);letter-spacing:-0.01em}
  81. .text-body{font-size:clamp(0.9rem,1.05vw,1rem);line-height:1.8;color:var(--text-2)}
  82. .text-sm{font-size:0.85rem;color:var(--text-2)}
  83. .text-xs{font-size:0.72rem;color:var(--text-3);letter-spacing:0.02em}
  84. .text-mono{font-family:var(--font-mono)}
  85. .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)}
  86. 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}
  87. 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}
  88. 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)}
  89. section.report-section.in-view::after{opacity:1}
  90. section.report-section[data-tone="amber"]{--slide-glow-1:rgba(245,166,35,0.10);--slide-glow-2:rgba(245,166,35,0.04)}
  91. section.report-section[data-tone="rose"]{--slide-glow-1:rgba(255,77,141,0.09);--slide-glow-2:rgba(255,77,141,0.04)}
  92. section.report-section[data-tone="green"]{--slide-glow-1:rgba(0,220,130,0.08);--slide-glow-2:rgba(0,220,130,0.03)}
  93. section.report-section[data-tone="purple"]{--slide-glow-1:rgba(139,92,246,0.10);--slide-glow-2:rgba(139,92,246,0.04)}
  94. section.report-section[data-tone="blue"]{--slide-glow-1:rgba(59,130,246,0.08);--slide-glow-2:rgba(59,130,246,0.03)}
  95. section.report-section[data-tone="orange"]{--slide-glow-1:rgba(249,115,22,0.10);--slide-glow-2:rgba(249,115,22,0.04)}
  96. section.report-section[data-tone="red"]{--slide-glow-1:rgba(239,68,68,0.10);--slide-glow-2:rgba(239,68,68,0.05)}
  97. .section-inner{width:100%;max-width:var(--max-w);margin:0 auto;position:relative}
  98. .section-head{margin-bottom:clamp(28px,4vh,56px)}
  99. .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:16px}
  100. .section-eyebrow::before{content:'';display:inline-block;width:24px;height:1px;background:var(--amber)}
  101. .section-sub{color:var(--text-2);font-size:1rem;max-width:720px;margin-top:12px;line-height:1.8}
  102. .grid{display:grid;gap:clamp(12px,1.4vw,20px)}
  103. .grid-2{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
  104. .grid-3{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
  105. .grid-4{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
  106. .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(18px,2vw,28px);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}
  107. .card:hover{border-color:var(--border-strong);transform:translateY(-2px);box-shadow:0 8px 32px rgba(0,0,0,0.3)}
  108. .card-accent{border-left:3px solid var(--amber)}
  109. .metric{background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(20px,2.2vw,32px);position:relative;overflow:hidden}
  110. .metric::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--amber),transparent);opacity:0.6}
  111. .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:8px}
  112. .metric .metric-value{font-family:var(--font-head);font-weight:800;font-size:clamp(2rem,3.5vw,3rem);letter-spacing:-0.03em;line-height:1;color:var(--text-1);margin-bottom:6px}
  113. .metric .metric-sub{margin-top:10px;font-size:0.8rem;color:var(--text-2);line-height:1.4}
  114. .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)}
  115. .tag.green{background:rgba(0,220,130,0.08);color:var(--green);border-color:rgba(0,220,130,0.2)}
  116. .tag.purple{background:rgba(139,92,246,0.08);color:var(--purple);border-color:rgba(139,92,246,0.2)}
  117. .tag.rose{background:rgba(255,77,141,0.08);color:var(--rose);border-color:rgba(255,77,141,0.2)}
  118. .tag.blue{background:rgba(59,130,246,0.08);color:var(--blue);border-color:rgba(59,130,246,0.2)}
  119. .tag.orange{background:rgba(249,115,22,0.08);color:var(--orange);border-color:rgba(249,115,22,0.2)}
  120. .tag.red{background:rgba(239,68,68,0.08);color:var(--red);border-color:rgba(239,68,68,0.2)}
  121. .tag.gray{background:rgba(107,107,107,0.1);color:var(--text-2);border-color:rgba(107,107,107,0.2)}
  122. .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)}
  123. .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%)}
  124. .footer{text-align:center;padding:40px 24px;color:var(--text-3);font-size:0.8rem;border-top:1px solid var(--border)}
  125. .progress-line{position:fixed;top:0;left:0;height:2px;background:linear-gradient(90deg,var(--amber),var(--rose));z-index:1000;width:0%;transition:width 0.3s ease}
  126. .nav-rail{position:fixed;left:clamp(16px,2vw,32px);top:50%;transform:translateY(-50%);z-index:100;display:flex;flex-direction:column;gap:10px}
  127. .nav-dot{width:28px;height:3px;border-radius:2px;background:rgba(255,255,255,0.14);cursor:pointer;transition:all 0.5s cubic-bezier(.2,.7,.2,1)}
  128. .nav-dot.active{background:var(--amber);width:40px;box-shadow:0 0 10px rgba(245,166,35,0.4)}
  129. .nav-dot::after{content:attr(data-tip);position:absolute;left:calc(100% + 12px);top:50%;transform:translateY(-50%);background:rgba(20,20,20,0.92);backdrop-filter:blur(8px);border:1px solid var(--border);padding:5px 10px;border-radius:6px;font-size:0.7rem;color:var(--text-1);white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.2s}
  130. .nav-dot:hover::after,.nav-dot.active::after{opacity:1}
  131. .slide-counter{position:fixed;left:clamp(16px,2vw,28px);bottom:clamp(16px,2vh,28px);z-index:100;display:flex;align-items:center;gap:8px;padding:8px 14px;border-radius:999px;background:rgba(20,20,20,0.85);backdrop-filter:blur(16px);border:1px solid var(--border-strong);font-family:var(--font-mono);font-size:0.74rem;letter-spacing:0.1em;color:var(--text-2)}
  132. .slide-counter .now{color:var(--amber);font-weight:700}
  133. .kbd-hint{position:fixed;right:clamp(16px,2vw,28px);bottom:clamp(16px,2vh,28px);z-index:100;display:flex;align-items:center;gap:10px;padding:10px 16px;border-radius:999px;background:rgba(20,20,20,0.85);backdrop-filter:blur(16px);border:1px solid var(--border-strong);font-family:var(--font-mono);font-size:0.72rem;color:var(--text-2);letter-spacing:0.04em}
  134. .kbd-hint kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;background:var(--bg-3);border:1px solid var(--border-strong);border-radius:4px;font-size:0.7rem;font-family:var(--font-mono);color:var(--text-1);box-shadow:0 1px 0 rgba(0,0,0,0.4)}
  135. @media(max-width:900px){.nav-rail{display:none}.slide-counter,.kbd-hint{display:none}}
  136. </style>
  137. </head>
  138. <body>
  139. <div class="progress-line" id="progressLine"></div>
  140. <nav class="nav-rail" id="navRail"></nav>
  141. <div class="slide-counter"><span class="now" id="slideNow">01</span><div style="width:60px;height:2px;background:rgba(255,255,255,0.1);border-radius:2px;overflow:hidden;"><div id="slideBar" style="height:100%;width:0%;background:linear-gradient(90deg,var(--amber),var(--rose));transition:width 0.6s cubic-bezier(.2,.7,.2,1);"></div></div><span id="slideTotal">09</span></div>
  142. <div class="kbd-hint"><kbd>↓</kbd><kbd>↑</kbd> 滚动</div>
  143. <main class="deck">
  144. <!-- 封面 -->
  145. <section class="report-section" data-slide-num="COVER" data-tone="amber" style="position:relative;min-height:100vh;">
  146. <div class="cover-grid-bg"></div>
  147. <div class="cover-hero">
  148. <div style="margin-bottom:20px;">
  149. <span class="tag rose" style="font-size:0.8rem;padding:6px 14px;">快手快聘 · 蓝领求职生态洞察</span>
  150. </div>
  151. <h1 class="text-hero" style="margin-bottom:16px;background:linear-gradient(135deg,#fff 30%,rgba(245,166,35,0.9) 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;">蓝领求职</h1>
  152. <h2 class="text-hero" style="margin-bottom:24px;background:linear-gradient(135deg,rgba(245,166,35,0.7) 0%,rgba(139,92,246,0.8) 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;">最担心什么?</h2>
  153. <p class="text-body" style="font-size:1.1rem;max-width:580px;margin-bottom:32px;line-height:1.8;">基于<span style="color:var(--amber);font-weight:600;">${meta.comments.toLocaleString()}</span>条真实评论的深度分析 · 揭示4亿蓝领劳动者的求职焦虑与核心诉求</p>
  154. <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:16px;max-width:800px;margin-bottom:32px;">
  155. <div style="padding:16px 18px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:var(--radius-md);">
  156. <div style="font-size:1.8rem;font-weight:800;color:var(--amber);font-family:var(--font-head);">2.5亿</div>
  157. <div class="text-xs" style="margin-top:4px;">蓝领月活用户</div>
  158. </div>
  159. <div style="padding:16px 18px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:var(--radius-md);">
  160. <div style="font-size:1.8rem;font-weight:800;color:var(--green);font-family:var(--font-head);">40万+</div>
  161. <div class="text-xs" style="margin-top:4px;">带岗主播</div>
  162. </div>
  163. <div style="padding:16px 18px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:var(--radius-md);">
  164. <div style="font-size:1.8rem;font-weight:800;color:var(--rose);font-family:var(--font-head);">${fmtLikes(topLiked[0]?.likes||0)}</div>
  165. <div class="text-xs" style="margin-top:4px;">最高点赞评论</div>
  166. </div>
  167. <div style="padding:16px 18px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:var(--radius-md);">
  168. <div style="font-size:1.8rem;font-weight:800;color:var(--purple);font-family:var(--font-head);">${hypos.length}</div>
  169. <div class="text-xs" style="margin-top:4px;">验证假设数</div>
  170. </div>
  171. </div>
  172. <div class="text-xs" style="margin-top:24px;color:var(--text-3);">${meta.collectedAt} · 数据来源:快手评论采集 · Generated by OpenClaw VOC System</div>
  173. </div>
  174. </section>
  175. <!-- Ch1: 核心问题 -->
  176. <section class="report-section" data-slide-num="01" data-tone="amber">
  177. <div class="section-inner">
  178. <div class="section-head">
  179. <div class="section-eyebrow">CHAPTER 01</div>
  180. <h2 class="text-h1">蓝领求职:最担心什么?</h2>
  181. <p class="section-sub">基于4,948条真实评论的情感分析与洞察</p>
  182. </div>
  183. <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:32px;">
  184. <div class="card" style="border-top:3px solid var(--rose);text-align:center;padding:24px;">
  185. <div style="font-size:2.5rem;margin-bottom:12px;">⚠️</div>
  186. <div style="font-weight:700;color:var(--rose);margin-bottom:8px;font-size:1.1rem;">求职被骗焦虑</div>
  187. <div style="font-size:2rem;font-weight:800;color:var(--rose);font-family:var(--font-head);">${byHypothesis.H6?.length||0}</div>
  188. <div class="text-xs" style="margin-top:6px;">条相关评论</div>
  189. </div>
  190. <div class="card" style="border-top:3px solid var(--red);text-align:center;padding:24px;">
  191. <div style="font-size:2.5rem;margin-bottom:12px;">🏴</div>
  192. <div style="font-weight:700;color:var(--red);margin-bottom:8px;font-size:1.1rem;">黑中介痛点</div>
  193. <div style="font-size:2rem;font-weight:800;color:var(--red);font-family:var(--font-head);">${byHypothesis.H7?.length||0}</div>
  194. <div class="text-xs" style="margin-top:6px;">条相关评论</div>
  195. </div>
  196. <div class="card" style="border-top:3px solid var(--green);text-align:center;padding:24px;">
  197. <div style="font-size:2.5rem;margin-bottom:12px;">💰</div>
  198. <div style="font-weight:700;color:var(--green);margin-bottom:8px;font-size:1.1rem;">薪资不透明</div>
  199. <div style="font-size:2rem;font-weight:800;color:var(--green);font-family:var(--font-head);">${byHypothesis.H3?.length||0}</div>
  200. <div class="text-xs" style="margin-top:6px;">条相关评论</div>
  201. </div>
  202. </div>
  203. <div class="card card-accent" style="padding:24px 28px;margin-bottom:24px;background:linear-gradient(135deg,rgba(255,77,141,0.06),transparent);">
  204. <div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
  205. <span style="font-size:1.8rem;">💡</span>
  206. <span style="font-weight:700;font-size:1.1rem;">核心洞察</span>
  207. </div>
  208. <p class="text-body">蓝领求职者最担心的是一个从<strong style="color:var(--rose);">"虚假信息"</strong>到<strong style="color:var(--red);">"财产欺诈"</strong>再到<strong style="color:var(--text-1);">"权益落空"</strong>的<strong style="color:var(--amber);">完整风险链条</strong>。他们最看重的是一套基于<strong style="color:var(--green);">"可视化真实"</strong>和<strong style="color:var(--green);">"熟人信任"</strong>,能够降低风险、简化流程、实现快速入职的解决方案。</p>
  209. </div>
  210. <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px;">
  211. <div style="padding:20px 22px;background:rgba(255,77,141,0.06);border:1px solid rgba(255,77,141,0.2);border-radius:var(--radius-md);">
  212. <div style="font-weight:600;margin-bottom:10px;color:var(--rose);">🚨 最担心:招聘信息虚假</div>
  213. <p class="text-sm" style="line-height:1.6;">"高薪诱惑""夸大福利""模糊劳动条件"——求职者入职后发现实际与招聘描述严重不符</p>
  214. </div>
  215. <div style="padding:20px 22px;background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.2);border-radius:var(--radius-md);">
  216. <div style="font-weight:600;margin-bottom:10px;color:var(--red);">💸 最担心:财产诈骗押金陷阱</div>
  217. <p class="text-sm" style="line-height:1.6;">直播间高薪诱惑,缴纳"入职押金"或"培训费"后招工方失联</p>
  218. </div>
  219. <div style="padding:20px 22px;background:rgba(0,220,130,0.06);border:1px solid rgba(0,220,130,0.2);border-radius:var(--radius-md);">
  220. <div style="font-weight:600;margin-bottom:10px;color:var(--green);">✓ 最看重:眼见为实</div>
  221. <p class="text-sm" style="line-height:1.6;">直播展示车间、宿舍、食堂,消解信息不对称带来的不安全感</p>
  222. </div>
  223. <div style="padding:20px 22px;background:rgba(59,130,246,0.06);border:1px solid rgba(59,130,246,0.2);border-radius:var(--radius-md);">
  224. <div style="font-weight:600;margin-bottom:10px;color:var(--blue);">✓ 最看重:零门槛求职</div>
  225. <p class="text-sm" style="line-height:1.6;">留下联系方式即可求职,无需撰写简历、填写复杂表格</p>
  226. </div>
  227. </div>
  228. </div>
  229. </section>
  230. <!-- Ch2: 求职被骗焦虑 -->
  231. <section class="report-section" data-slide-num="02" data-tone="rose">
  232. <div class="section-inner">
  233. <div class="section-head">
  234. <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 02</div>
  235. <h2 class="text-h1">求职被骗焦虑</h2>
  236. <p class="section-sub">H6 · ${byHypothesis.H6?.length||0}条评论揭示的普遍性恐惧</p>
  237. </div>
  238. <div class="grid grid-2" style="margin-bottom:28px;">
  239. ${insightBanner('01','虚假宣传与货不对版','求职者极度担心招聘信息中承诺的薪资、食宿条件、工作强度与入职后实际情况严重不符。承诺月薪近万,实际到手可能仅4000多元。','6276','最高点赞', '#FF4D8D')}
  240. ${insightBanner('02','诈骗风险与押金陷阱','直播间看到高薪日结岗位,缴纳"入职押金"或"培训费"后招工方失联。前科主播利用虚假人设和"官方合作"名义行骗。','422','次高点赞', '#EF4444')}
  241. </div>
  242. <div style="margin-bottom:24px;">
  243. <div class="section-eyebrow" style="color:var(--rose);margin-bottom:14px;">⚠️ 高风险焦虑类 VOC 样本</div>
  244. <div class="grid grid-2">
  245. ${highRiskVOC.slice(0,6).map(v=>vocCard(v, true)).join('')}
  246. </div>
  247. </div>
  248. <div class="card" style="padding:20px 24px;border-left:3px solid var(--rose);">
  249. <div style="font-weight:700;margin-bottom:10px;color:var(--rose);">📊 评论区典型声音</div>
  250. <p class="text-body">"地铁安检我知道,找中介还不如直接去地铁口问招不招人" —— 折射出求职者对中介渠道的深度不信任</p>
  251. <p class="text-body" style="margin-top:10px;">"我说老实话,我没工作也不找中介,全中国中介最黑" —— 对中介生态的系统性质疑</p>
  252. </div>
  253. </div>
  254. </section>
  255. <!-- Ch3: 黑中介痛点 -->
  256. <section class="report-section" data-slide-num="03" data-tone="red">
  257. <div class="section-inner">
  258. <div class="section-head">
  259. <div class="section-eyebrow" style="color:var(--red);">CHAPTER 03</div>
  260. <h2 class="text-h1">黑中介是核心痛点</h2>
  261. <p class="section-sub">H7 · ${byHypothesis.H7?.length||0}条评论 · 最集中的负面情绪</p>
  262. </div>
  263. ${barChart([
  264. {label:'押金不退',value:115,color:'#EF4444'},
  265. {label:'黑中介套路',value:157,color:'#F97316'},
  266. {label:'入职被坑',value:109,color:'#FF4D8D'},
  267. {label:'求职被骗',value:31,color:'#8B5CF6'},
  268. ],'#EF4444')}
  269. <div style="margin-top:28px;margin-bottom:24px;">
  270. <div class="section-eyebrow" style="color:var(--red);margin-bottom:14px;">🏴 黑中介类 VOC 高赞精选</div>
  271. <div class="grid grid-2">
  272. ${highRiskVOC.filter(v=>['黑中介','押金不退'].includes(v.keyword)).slice(0,6).map(v=>vocCard(v, true)).join('')}
  273. </div>
  274. </div>
  275. <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px;">
  276. <div class="card" style="padding:20px 22px;border-top:3px solid var(--red);">
  277. <div style="font-weight:700;margin-bottom:10px;color:var(--red);">🔴 押金不退</div>
  278. <p class="text-sm" style="line-height:1.6;margin-bottom:12px;">租房押金、入职押金被扣押或不退还,是评论区最高频的投诉类型之一</p>
  279. <div style="padding:10px 14px;background:rgba(239,68,68,0.08);border-radius:6px;">
  280. <span style="font-size:1.2rem;font-weight:800;color:var(--red);">2209</span>
  281. <span class="text-xs" style="margin-left:8px;">最高点赞</span>
  282. </div>
  283. </div>
  284. <div class="card" style="padding:20px 22px;border-top:3px solid var(--orange);">
  285. <div style="font-weight:700;margin-bottom:10px;color:var(--orange);">🟠 中介套路</div>
  286. <p class="text-sm" style="line-height:1.6;margin-bottom:12px;">收身份证、扣押证件、高额中介费、承诺不兑现等套路层出不穷</p>
  287. <div style="padding:10px 14px;background:rgba(249,115,22,0.08);border-radius:6px;">
  288. <span style="font-size:1.2rem;font-weight:800;color:var(--orange);">491</span>
  289. <span class="text-xs" style="margin-left:8px;">相关最高点赞</span>
  290. </div>
  291. </div>
  292. </div>
  293. </div>
  294. </section>
  295. <!-- Ch4: 薪资透明度 -->
  296. <section class="report-section" data-slide-num="04" data-tone="green">
  297. <div class="section-inner">
  298. <div class="section-head">
  299. <div class="section-eyebrow" style="color:var(--green);">CHAPTER 04</div>
  300. <h2 class="text-h1">薪资透明度是核心关切</h2>
  301. <p class="section-sub">H3 · ${byHypothesis.H3?.length||0}条评论 · 贯穿求职决策全链路</p>
  302. </div>
  303. <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:28px;">
  304. ${insightBanner('01','承诺月薪vs到手工资','求职者担心实际到手薪资与招聘承诺差距巨大。评论区出现大量"7天不给工资""领导说话难听"的控诉。','86','最高点赞', '#00DC82')}
  305. ${insightBanner('02','加班费计算不透明','蓝领岗位薪资结构复杂,加班费、绩效、扣款等明细不清晰,导致实际收入与预期不符','51','次高点赞', '#3B82F6')}
  306. </div>
  307. <div style="margin-bottom:24px;">
  308. <div class="section-eyebrow" style="color:var(--green);margin-bottom:14px;">💰 薪资焦虑类 VOC 精选</div>
  309. <div class="grid grid-2">
  310. ${topLiked.filter(v=>['工资靠谱','真实薪资'].includes(v.keyword)).slice(0,6).map(v=>vocCard(v, true)).join('')}
  311. </div>
  312. </div>
  313. <div class="card" style="padding:20px 24px;border-left:3px solid var(--green);">
  314. <div style="font-weight:700;margin-bottom:10px;color:var(--green);">📈 薪资公平性讨论</div>
  315. <p class="text-body">"6000倍不止,恒大夏总月工资几千万,老农民月二百" —— 折射出蓝领群体对薪资差距的社会情绪</p>
  316. <p class="text-body" style="margin-top:10px;">"农村扫大街每天十元还有赊账" —— 底层劳动者收入微薄,对薪资保障的强烈渴望</p>
  317. </div>
  318. </div>
  319. </section>
  320. <!-- Ch5: 快聘平台优势 -->
  321. <section class="report-section" data-slide-num="05" data-tone="blue">
  322. <div class="section-inner">
  323. <div class="section-head">
  324. <div class="section-eyebrow" style="color:var(--blue);">CHAPTER 05</div>
  325. <h2 class="text-h1">"10个蓝领,9个都用快手"</h2>
  326. <p class="section-sub">快手快聘的平台战略优势与差异化定位</p>
  327. </div>
  328. <div class="grid grid-4" style="margin-bottom:32px;">
  329. <div class="metric"><div class="metric-label">蓝领月活</div><div class="metric-value" style="color:var(--amber);">2.5亿</div><div class="metric-sub">去重MAU</div></div>
  330. <div class="metric"><div class="metric-label">带岗主播</div><div class="metric-value" style="color:var(--green);">40万+</div><div class="metric-sub">专职主播</div></div>
  331. <div class="metric"><div class="metric-label">入驻企业</div><div class="metric-value" style="color:var(--blue);">24万</div><div class="metric-sub">用人单位</div></div>
  332. <div class="metric"><div class="metric-label">年求职线索</div><div class="metric-value" style="color:var(--purple);">3.47亿</div><div class="metric-sub">2024全年</div></div>
  333. </div>
  334. <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:28px;">
  335. <div class="card" style="border-top:3px solid var(--amber);text-align:center;">
  336. <div style="font-size:2.2rem;margin-bottom:12px;">👁️</div>
  337. <div style="font-weight:700;color:var(--amber);margin-bottom:8px;">"眼见为实"</div>
  338. <p class="text-sm" style="line-height:1.6;">直播走进车间、宿舍、食堂,展示真实工作环境</p>
  339. </div>
  340. <div class="card" style="border-top:3px solid var(--green);text-align:center;">
  341. <div style="font-size:2.2rem;margin-bottom:12px;">🤝</div>
  342. <div style="font-weight:700;color:var(--green);margin-bottom:8px;">"老铁信任"</div>
  343. <p class="text-sm" style="line-height:1.6;">主播与粉丝长期建立熟人推荐式信任关系</p>
  344. </div>
  345. <div class="card" style="border-top:3px solid var(--blue);text-align:center;">
  346. <div style="font-size:2.2rem;margin-bottom:12px;">⚡</div>
  347. <div style="font-weight:700;color:var(--blue);margin-bottom:8px;">"零门槛"</div>
  348. <p class="text-sm" style="line-height:1.6;">留联系方式即可求职,无需简历复杂填报</p>
  349. </div>
  350. </div>
  351. <div class="card" style="padding:24px 28px;background:linear-gradient(135deg,rgba(59,130,246,0.06),transparent);">
  352. <div style="font-weight:700;margin-bottom:12px;">💡 快聘战略地位</div>
  353. <p class="text-body" style="margin-bottom:12px;">快聘在快手内部被视为<strong style="color:var(--amber);">仅次于电商的第二大战略业务板块</strong>。日活简历投递量达数十万级,2025年Q1日均投递用户数同比增长超110%。</p>
  354. <p class="text-body">CEO程一笑披露:快招工板块去重月活跃用户规模已达到2.5亿,蓝领招聘线上渗透率到2025-2026年可达30%,市场规模有望达到千亿人民币。</p>
  355. </div>
  356. </div>
  357. </section>
  358. <!-- Ch6: 假设验证 -->
  359. <section class="report-section" data-slide-num="06" data-tone="purple">
  360. <div class="section-inner">
  361. <div class="section-head">
  362. <div class="section-eyebrow" style="color:var(--purple);">CHAPTER 06</div>
  363. <h2 class="text-h1">H1-H8 假设验证</h2>
  364. <p class="section-sub">基于${meta.comments.toLocaleString()}条VOC的假设覆盖分析</p>
  365. </div>
  366. ${barChart(hypos.map(h=>({label:h.h+' '+h.title.slice(0,6),value:h.count,color:h.color})),'#8B5CF6')}
  367. <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-top:24px;">
  368. ${hypos.map(h=>`<div class="card" style="padding:16px 18px;border-top:2px solid ${h.color};">
  369. <div style="font-family:var(--font-mono);font-size:0.68rem;color:${h.color};margin-bottom:6px;font-weight:600;">${h.h}</div>
  370. <div style="font-size:0.85rem;line-height:1.4;margin-bottom:8px;">${h.title}</div>
  371. <div><span style="font-size:1.4rem;font-weight:800;color:${h.color};font-family:var(--font-head);">${h.count}</span><span class="text-xs" style="margin-left:4px;">条</span></div>
  372. </div>`).join('')}
  373. </div>
  374. <div class="grid grid-2" style="margin-top:24px;">
  375. <div class="card" style="padding:20px 22px;border-left:3px solid var(--green);">
  376. <div style="font-weight:700;color:var(--green);margin-bottom:10px;">✓ H1+H2 验证:快聘是主阵地</div>
  377. <p class="text-sm" style="line-height:1.6;">${byHypothesis.H1?.length||0}条评论覆盖。蓝领求职者确实将快手作为主要求职渠道,"10个蓝领9个用快手"得到验证。</p>
  378. </div>
  379. <div class="card" style="padding:20px 22px;border-left:3px solid var(--purple);">
  380. <div style="font-weight:700;color:var(--purple);margin-bottom:10px;">✓ H3+H4+H8 验证:薪资与权益是核心关切</div>
  381. <p class="text-sm" style="line-height:1.6;">${(byHypothesis.H3?.length||0)+(byHypothesis.H4?.length||0)+(byHypothesis.H8?.length||0)}条评论覆盖。薪资透明度、食宿条件、结算方式是求职决策的关键因素。</p>
  382. </div>
  383. </div>
  384. </div>
  385. </section>
  386. <!-- Ch7: 关键词分析 -->
  387. <section class="report-section" data-slide-num="07" data-tone="orange">
  388. <div class="section-inner">
  389. <div class="section-head">
  390. <div class="section-eyebrow" style="color:var(--orange);">CHAPTER 07</div>
  391. <h2 class="text-h1">关键词VOC覆盖</h2>
  392. <p class="section-sub">12个关键词的有效评论分布与情感倾向</p>
  393. </div>
  394. <div class="grid grid-3" style="margin-bottom:28px;">
  395. ${byKeyword.slice(0,6).map(kw=>`<div class="card" style="border-left:3px solid ${kw.items[0]?.likes > 100 ? 'var(--rose)' : 'var(--amber)'};">
  396. <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
  397. <div style="font-weight:700;">${kw.keyword}</div>
  398. <span class="tag orange">${kw.total}条</span>
  399. </div>
  400. ${kw.items.slice(0,3).map(v=>`<div style="font-size:0.8rem;color:var(--text-2);line-height:1.5;margin-bottom:8px;padding:8px 10px;background:var(--bg-2);border-radius:4px;">
  401. <span style="color:var(--amber);font-weight:600;font-family:var(--font-mono);font-size:0.72rem;">♥${fmtLikes(v.likes)}</span>
  402. "${truncate(v.content||'',50)}"
  403. </div>`).join('')}
  404. </div>`).join('')}
  405. </div>
  406. <div class="card" style="padding:20px 24px;border-left:3px solid var(--rose);">
  407. <div style="font-weight:700;margin-bottom:10px;color:var(--rose);">⚠️ 风险类关键词占比最高</div>
  408. <p class="text-body">"黑中介(157条)"、"押金不退(115条)"、"入职被坑(109条)" —— 风险与痛点类关键词的有效评论占比显著高于正面需求类关键词,反映出蓝领求职生态中信任危机的普遍性。</p>
  409. </div>
  410. </div>
  411. </section>
  412. <!-- Ch8: 高赞评论 -->
  413. <section class="report-section" data-slide-num="08" data-tone="green">
  414. <div class="section-inner">
  415. <div class="section-head">
  416. <div class="section-eyebrow" style="color:var(--green);">CHAPTER 08</div>
  417. <h2 class="text-h1">高赞评论 TOP30</h2>
  418. <p class="section-sub">真实用户声音,点赞最高的评论</p>
  419. </div>
  420. <div class="grid grid-2">
  421. ${topLiked.slice(0,14).map(v=>vocCard(v, true)).join('')}
  422. </div>
  423. </div>
  424. </section>
  425. <!-- Ch9: 策略建议 -->
  426. <section class="report-section" data-slide-num="09" data-tone="amber">
  427. <div class="section-inner">
  428. <div class="section-head">
  429. <div class="section-eyebrow">CHAPTER 09</div>
  430. <h2 class="text-h1">信任重建策略</h2>
  431. <p class="section-sub">基于VOC洞察的平台与品牌建议</p>
  432. </div>
  433. <div class="grid grid-2" style="margin-bottom:28px;">
  434. <div class="card" style="border-top:3px solid var(--red);">
  435. <div style="font-size:1.8rem;margin-bottom:12px;">🚨</div>
  436. <div style="font-weight:700;color:var(--red);margin-bottom:10px;">平台信任危机</div>
  437. <p class="text-sm" style="line-height:1.6;margin-bottom:14px;">快手对主播宣传的招聘信息基本零审核,"甚至不如被诟病多年的58同城"。有前科主播借"官方合作"名义行骗事件已发生。</p>
  438. <span class="tag red">监管风险加剧</span>
  439. </div>
  440. <div class="card" style="border-top:3px solid var(--orange);">
  441. <div style="font-size:1.8rem;margin-bottom:12px;">📉</div>
  442. <div style="font-weight:700;color:var(--orange);margin-bottom:10px;">B端直招占比低</div>
  443. <p class="text-sm" style="line-height:1.6;margin-bottom:14px;">直播间以劳务中介为主,企业自招较少,对求职者直接吸引力有限。</p>
  444. <span class="tag orange">结构性问题</span>
  445. </div>
  446. </div>
  447. <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:24px;">
  448. <div class="card" style="padding:18px 16px;border-left:3px solid var(--green);text-align:center;">
  449. <div style="font-size:1.4rem;margin-bottom:8px;">✓</div>
  450. <div style="font-weight:700;margin-bottom:6px;font-size:0.9rem;">零押金承诺</div>
  451. <p class="text-xs" style="line-height:1.5;">主动公示"不收押金"政策</p>
  452. </div>
  453. <div class="card" style="padding:18px 16px;border-left:3px solid var(--blue);text-align:center;">
  454. <div style="font-size:1.4rem;margin-bottom:8px;">✓</div>
  455. <div style="font-weight:700;margin-bottom:6px;font-size:0.9rem;">企业直招</div>
  456. <p class="text-xs" style="line-height:1.5;">提高B端企业直招占比</p>
  457. </div>
  458. <div class="card" style="padding:18px 16px;border-left:3px solid var(--purple);text-align:center;">
  459. <div style="font-size:1.4rem;margin-bottom:8px;">✓</div>
  460. <div style="font-weight:700;margin-bottom:6px;font-size:0.9rem;">眼见为实</div>
  461. <p class="text-xs" style="line-height:1.5;">直播间展示真实工作环境</p>
  462. </div>
  463. <div class="card" style="padding:18px 16px;border-left:3px solid var(--amber);text-align:center;">
  464. <div style="font-size:1.4rem;margin-bottom:8px;">✓</div>
  465. <div style="font-weight:700;margin-bottom:6px;font-size:0.9rem;">老铁背书</div>
  466. <p class="text-xs" style="line-height:1.5;">入职老员工真实体验分享</p>
  467. </div>
  468. </div>
  469. <div class="card card-accent" style="padding:20px 24px;">
  470. <div style="font-weight:700;margin-bottom:10px;">⚠️ 监管动向</div>
  471. <p class="text-body">2026年4月,人力资源社会保障部等五部门对快手等平台开展行政指导,要求做到"五个到位":资质审核到位、信息明示到位、内容审核到位、风险防范到位、处置管控到位。</p>
  472. </div>
  473. </div>
  474. </section>
  475. </main>
  476. <footer class="footer">
  477. <div>快手快聘 · 蓝领求职洞察报告</div>
  478. <div style="margin-top:8px;">数据来源:快手评论采集 · ${meta.comments.toLocaleString()} 条VOC · Generated by OpenClaw</div>
  479. <div style="margin-top:4px;">${new Date().toLocaleDateString('zh-CN')}</div>
  480. </footer>
  481. <script>
  482. (function(){
  483. const sections=document.querySelectorAll('section.report-section');
  484. const total=sections.length;
  485. const progressLine=document.getElementById('progressLine');
  486. const slideNow=document.getElementById('slideNow');
  487. const slideBar=document.getElementById('slideBar');
  488. const navRail=document.getElementById('navRail');
  489. const kbdHint=document.querySelector('.kbd-hint');
  490. const navDots=[];
  491. let currentSlide=0;
  492. document.getElementById('slideTotal').textContent=String(total).padStart(2,'0');
  493. sections.forEach((sec,i)=>{
  494. const dot=document.createElement('div');
  495. dot.className='nav-dot';
  496. dot.dataset.tip=sec.querySelector('.text-h1')?.textContent||'0'+(i+1);
  497. dot.addEventListener('click',()=>sections[i].scrollIntoView({behavior:'smooth'}));
  498. navRail.appendChild(dot);
  499. navDots.push(dot);
  500. });
  501. const observer=new IntersectionObserver((entries)=>{
  502. entries.forEach(entry=>{
  503. if(entry.isIntersecting){
  504. entry.target.classList.add('in-view');
  505. const idx=Array.from(sections).indexOf(entry.target);
  506. currentSlide=idx;
  507. updateUI(idx);
  508. }
  509. });
  510. },{threshold:0.5});
  511. sections.forEach(sec=>observer.observe(sec));
  512. function updateUI(idx){
  513. const pct=((idx+1)/total)*100;
  514. progressLine.style.width=pct+'%';
  515. slideNow.textContent=String(idx+1).padStart(2,'0');
  516. slideBar.style.width=pct+'%';
  517. navDots.forEach((dot,i)=>dot.classList.toggle('active',i===idx));
  518. }
  519. setTimeout(()=>{kbdHint.classList.add('fade');},6000);
  520. let ticking=false;
  521. window.addEventListener('scroll',()=>{
  522. if(!ticking){
  523. requestAnimationFrame(()=>{
  524. const scrollTop=window.scrollY;
  525. const docHeight=document.documentElement.scrollHeight-window.innerHeight;
  526. const scrollPct=(scrollTop/docHeight)*100;
  527. progressLine.style.width=scrollPct+'%';
  528. ticking=false;
  529. });
  530. ticking=true;
  531. }
  532. });
  533. document.querySelectorAll('.card').forEach(c=>{
  534. c.addEventListener('mouseenter',()=>{
  535. document.body.style.setProperty('--amb-x',(Math.random()*30+10)+'%');
  536. document.body.style.setProperty('--amb-y',(Math.random()*30+10)+'%');
  537. });
  538. });
  539. })(document);
  540. </script>
  541. </body>
  542. </html>`;
  543. if (!fs.existsSync(OUT_DIR)) {
  544. fs.mkdirSync(OUT_DIR, { recursive: true });
  545. }
  546. fs.writeFileSync(OUT_FILE, html, 'utf8');
  547. console.log('✅ 快手快聘 VOC 报告已生成: ' + OUT_FILE);
  548. console.log(' 有效VOC数据: ' + meta.comments.toLocaleString() + ' 条');
  549. console.log(' H假设覆盖: ' + hypos.map(h=>h.h+':'+h.count).join(', '));