| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- // ==============================================================================
- // <品类名> · Ch<N> · <章主题>(模板)
- // ==============================================================================
- // 使用方法:
- // 1. 复制此文件为 <品类>-ch<N>-<topic>.js
- // 2. 替换所有 <...> 占位符
- // 3. 按实际子章数填充 renderSubN 函数
- // 4. 在 module.exports 里映射 '<key>-<subIdx>': renderSubN
- // 5. 在 render.js 的 SLIDE_RENDERERS 里导入此文件
- //
- // 每个子章必含 5 件套:
- // ① SectionHead(eyebrow + title + subtitle)
- // ② ≥ 3 张 VOC 证据卡(来自 getEvidence)
- // ③ 至少 1 个结构化组件(表格 / 矩阵 / 统计条)
- // ④ DecisionList 落地决策
- // ⑤ data-slide-num 属性
- // ==============================================================================
- const LA = require('./<品类>-analyze.js');
- const C = require('./<品类>-components.js');
- const { BRAND, esc, fmtLikes } = C;
- // ==========================================================
- // Sub 1 · <子章 1 主题>
- // ==========================================================
- function renderSub1(ctx) {
- const { chapter, subIdx, sub, slideNum, slideTotal, data } = ctx;
- const items = data.items || [];
- // ① 查询证据
- const coreVoc = LA.getEvidence(items, {
- keyword: '<本品/竞品名>',
- contentMatch: /<相关词 1>|<相关词 2>|<相关词 3>/,
- minChars: 25,
- top: 4,
- seed: 7, // 种子用奇数,避免和其他章冲突
- });
- const painVoc = LA.getEvidence(items, {
- contentMatch: /<痛点词 1>|<痛点词 2>/,
- sentiment: 'negative',
- minChars: 25,
- top: 3,
- seed: 11,
- });
- // ② 渲染 HTML
- return `<section class="report-section" id="${chapter.key}-${subIdx}"
- data-tone="${chapter.tone}" data-slide-num="${slideNum}/${slideTotal}">
- <div class="section-inner">
- ${C.renderSectionHead({
- eyebrow: `${chapter.num}.${subIdx} · <副标题>`,
- title: '<span style="color:' + BRAND.MAIN + ';"><关键词></span>:<主标题>',
- subtitle: '<副标题描述 · 30-80 字解释 subchapter 要讲什么>',
- color: BRAND.MAIN,
- })}
- <!-- VOC 证据卡 · 核心 -->
- <div class="grid" style="grid-template-columns:1fr 1fr; gap:14px; margin:20px 0;">
- ${coreVoc.map((it) => C.renderVocCard(it, { accent: BRAND.MAIN, maxChars: 200 })).join('')}
- </div>
- <!-- KPI / 统计条 -->
- ${C.renderStatStrip([
- { num: '<数字 1>', label: '<说明>', color: BRAND.MAIN },
- { num: '<数字 2>', label: '<说明>', color: BRAND.JIANG_GREEN },
- { num: '<数字 3>', label: '<说明>', color: BRAND.CORAL },
- { num: '<数字 4>', label: '<说明>', color: BRAND.PURPLE },
- ], { accent: BRAND.MAIN })}
- <!-- Insight Hero · 金句 -->
- ${C.renderInsightHero({
- eyebrow: '💡 KEY INSIGHT · 关键洞察',
- title: '<一句话金句观点(25-50 字,带 strong 强调)>',
- subtitle: '<进一步解释或证据补充>',
- color: BRAND.MAIN,
- })}
- <!-- 痛点 VOC 二排 -->
- <div class="grid" style="grid-template-columns:1fr 1fr 1fr; gap:14px; margin:24px 0;">
- ${painVoc.map((it) => C.renderVocCard(it, { accent: BRAND.CORAL, compact: true, maxChars: 140 })).join('')}
- </div>
- <!-- 落地决策 -->
- ${C.renderDecisionList([
- `<strong>P0 · 决策 1</strong>:<具体动作>`,
- `<strong>P1 · 决策 2</strong>:<具体动作>`,
- `<strong>P2 · 决策 3</strong>:<具体动作>`,
- `<strong>P3 · 决策 4</strong>:<具体动作>`,
- ], { title: '落地决策', accent: BRAND.MAIN, columns: 2 })}
- </div>
- </section>`;
- }
- // ==========================================================
- // Sub 2 · <子章 2 主题>
- // ==========================================================
- function renderSub2(ctx) {
- const { chapter, subIdx, sub, slideNum, slideTotal, data } = ctx;
- const items = data.items || [];
- // 以竞品对比为例
- const voc = LA.getEvidence(items, {
- keyword: '<竞品名>',
- top: 4,
- seed: 13,
- });
- return `<section class="report-section" id="${chapter.key}-${subIdx}"
- data-tone="${chapter.tone}" data-slide-num="${slideNum}/${slideTotal}">
- <div class="section-inner">
- ${C.renderSectionHead({
- eyebrow: `${chapter.num}.${subIdx} · <子章副标题>`,
- title: '<主标题>',
- color: BRAND.CORAL,
- })}
- <!-- 对比表格 -->
- ${C.renderCompareTable({
- title: 'COMPETITOR COMPARISON',
- headers: ['维度', '本品', '<竞品 A>', '<竞品 B>'],
- rows: [
- [
- '核心成分',
- { html: '<strong>本品</strong>', color: BRAND.MAIN, bold: true },
- { html: '<竞品 A>', color: 'var(--text-2)' },
- { html: '<竞品 B>', color: 'var(--text-2)' },
- ],
- ['价格', '¥<...>', '¥<...>', '¥<...>'],
- ['批号', 'OTC', '食品', '保健品'],
- ],
- accent: BRAND.CORAL,
- })}
- <!-- VOC 证据 -->
- <div class="grid" style="grid-template-columns:1fr 1fr; gap:14px; margin:24px 0;">
- ${voc.map((it) => C.renderVocCard(it, { accent: BRAND.CORAL })).join('')}
- </div>
- ${C.renderDecisionList([
- `<strong>决策 1</strong>:<...>`,
- `<strong>决策 2</strong>:<...>`,
- ], { accent: BRAND.CORAL })}
- </div>
- </section>`;
- }
- // ==========================================================
- // Sub 3 · <子章 3 主题>(2×2 矩阵示例)
- // ==========================================================
- function renderSub3(ctx) {
- const { chapter, subIdx, sub, slideNum, slideTotal, data } = ctx;
- return `<section class="report-section" id="${chapter.key}-${subIdx}"
- data-tone="${chapter.tone}" data-slide-num="${slideNum}/${slideTotal}">
- <div class="section-inner">
- ${C.renderSectionHead({
- eyebrow: `${chapter.num}.${subIdx} · 无人地带坐标`,
- title: '<主标题>',
- color: BRAND.JIANG_GREEN,
- })}
- ${C.renderMatrix2x2({
- xLabel: '<X 轴名>',
- yLabel: '<Y 轴名>',
- xLow: '<X 低端>',
- xHigh: '<X 高端>',
- yLow: '<Y 低端>',
- yHigh: '<Y 高端>',
- accent: BRAND.JIANG_GREEN,
- quadrants: {
- q1: {
- title: '右上 · <象限 1>',
- color: BRAND.TECH_BLUE,
- items: ['<竞品 A>', '<竞品 B>'],
- note: '痛点:<...>',
- },
- q2: {
- title: '左上 · <象限 2>',
- color: BRAND.WINE_RED,
- items: ['<竞品 C>'],
- note: '痛点:<...>',
- },
- q3: {
- title: '左下 · <象限 3>',
- color: BRAND.WARM_ORANGE,
- items: ['<竞品 D>'],
- note: '<...>',
- },
- q4: {
- title: '右下 · <象限 4>(<strong style="color:' + BRAND.JIANG_GREEN + ';">无人地带</strong>)',
- color: BRAND.JIANG_GREEN,
- items: ['<strong>✨ 本品独占</strong>'],
- note: '✅ 机会:<...>',
- },
- },
- })}
- ${C.renderDecisionList([
- `<strong>锁定象限</strong>:<...>`,
- `<strong>瓦解对手</strong>:<...>`,
- ], { accent: BRAND.JIANG_GREEN })}
- </div>
- </section>`;
- }
- // ==========================================================
- // 导出
- // TODO: 按实际 subs 数量增减
- // ==========================================================
- module.exports = {
- '<chapter-key>-1': renderSub1,
- '<chapter-key>-2': renderSub2,
- '<chapter-key>-3': renderSub3,
- // '<chapter-key>-4': renderSub4, // 如有更多子章
- // '<chapter-key>-5': renderSub5,
- };
|