// ============================================================================== // <品类名> · VOC 报告 UI 组件库(模板) // ============================================================================== // 职责: // - BRAND 色板(品类专属) // - 10 个核心组件(Cover / Agenda / Divider / SectionHead / VocCard // StatStrip / CompareTable / Matrix2x2 / InsightHero / DecisionList) // - esc/fmtLikes 等工具 // ============================================================================== // ========================================================== // 1. BRAND 色板(按品类定制) // TODO: 修改为当前品类的主题色 // ========================================================== const BRAND = { // 品类主色 MAIN: '#4A90E2', MAIN_GLOW: 'rgba(74,144,226,0.15)', // 品牌背书色(江中国货等) JIANG_GREEN: '#1A7C5F', OTC_BLUE: '#0071CE', // 情绪表达色 CORAL: '#FF6B6B', // 痛点 / 焦虑 LACTIC_MINT: '#8FD3B8', // 平和 / 解法 WINE_RED: '#722F37', // 反向 / 禁区 GOLD: '#D4AF37', // 尊贵 / 溢价 // 辅助色 TECH_BLUE: '#4A90E2', PURPLE: '#8B5CF6', SAGE: '#9CAF88', BERRY_PINK: '#E63E7B', ROSE: '#FF4D8D', SKY_BLUE: '#56CCF2', WARM_ORANGE: '#FF9F43', SUNNY_YELLOW: '#FFCC33', // 分龄色(儿童品类可选) BABY_PEACH: '#FFB6A3', PRESCHOOL_LIME: '#B5E48C', SCHOOL_LAVENDER: '#B5A3E0', }; // ========================================================== // 2. 工具函数 // ========================================================== function esc(s) { return String(s || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function fmtLikes(n) { if (!n) return '0'; if (n >= 10000) return (n / 10000).toFixed(1) + 'w'; if (n >= 1000) return (n / 1000).toFixed(1) + 'k'; return String(n); } // ========================================================== // 3. renderVocCard · VOC 证据卡(核心组件) // ========================================================== function renderVocCard(item, opts = {}) { if (!item) return ''; const accent = opts.accent || BRAND.MAIN; const maxChars = opts.maxChars || 200; const content = String(item.content || '').slice(0, maxChars); const more = (item.content || '').length > maxChars ? '…' : ''; return `
${(item.platform || 'XHS').toUpperCase()} · ${esc(item.nickname || '匿名')} ${item.ip ? ` · ${esc(item.ip)}` : ''}
♥${fmtLikes(item.likes)}
「${esc(content)}${more}」
`; } // ========================================================== // 4. renderSectionHead · 子章节 Hero // ========================================================== function renderSectionHead(opts = {}) { const { eyebrow, title, subtitle, color = BRAND.MAIN } = opts; return `
${eyebrow ? `
${eyebrow}
` : ''}

${title || ''}

${subtitle ? `
${subtitle}
` : ''}
`; } // ========================================================== // 5. renderStatStrip · KPI 数字条 // ========================================================== function renderStatStrip(stats, opts = {}) { const accent = opts.accent || BRAND.MAIN; return `
${stats.map((s) => `
${esc(s.num)}
${esc(s.label)}
${s.delta ? `
${esc(s.delta)}
` : ''}
`).join('')}
`; } // ========================================================== // 6. renderDecisionList · 落地决策列表(每子章收尾) // ========================================================== function renderDecisionList(items, opts = {}) { const accent = opts.accent || BRAND.MAIN; const title = opts.title || '落地决策'; const columns = opts.columns || 2; return `
🎯 ${esc(title)}
${items.map((x) => `
${x}
`).join('')}
`; } // ========================================================== // 7. renderInsightHero · 金句观点卡 // ========================================================== function renderInsightHero(opts = {}) { const { eyebrow, title, subtitle, color = BRAND.MAIN } = opts; return `
${eyebrow ? `
${eyebrow}
` : ''}
${title || ''}
${subtitle ? `
${subtitle}
` : ''}
`; } // ========================================================== // 8. renderCompareTable · 对比表格 // ========================================================== function renderCompareTable(opts = {}) { const { title, headers = [], rows = [], accent = BRAND.MAIN } = opts; return `
${title ? `
${esc(title)}
` : ''}
${headers.map((h) => ``).join('')} ${rows.map((row) => ` ${row.map((cell) => { const c = typeof cell === 'object' ? cell : { html: cell }; return ``; }).join('')} `).join('')}
${esc(h)}
${c.html || c}
`; } // ========================================================== // 9. renderMatrix2x2 · 无人地带四象限 // ========================================================== function renderMatrix2x2(opts = {}) { const { xLabel, yLabel, xLow, xHigh, yLow, yHigh, quadrants = {}, accent = BRAND.MAIN } = opts; const renderQ = (q) => { if (!q) return '
'; return `
${q.title || ''}
${q.note ? `
${esc(q.note)}
` : ''}
`; }; return `
${esc(xLow || '')}
${esc(xHigh || '')}
${esc(yHigh || '')}
${renderQ(quadrants.q2)} ${renderQ(quadrants.q1)}
${esc(yLow || '')}
${renderQ(quadrants.q3)} ${renderQ(quadrants.q4)}
← ${esc(xLabel || '')} →
${yLabel ? `
Y: ${esc(yLabel)}
` : ''}
`; } // ========================================================== // 10. renderTheoryBox · 理论盒子(mapping 类) // ========================================================== function renderTheoryBox(opts = {}) { const { theory, accent = BRAND.MAIN, mapping } = opts; return `
${theory ? `
${esc(theory)}
` : ''} ${mapping || ''}
`; } // ========================================================== // 11. renderFindingCard · 发现洞察卡 // ========================================================== function renderFindingCard(opts = {}) { const { icon, title, body, accent = BRAND.MAIN } = opts; return `
${icon ? `
${icon}
` : ''}
${title ? `
${title}
` : ''}
${body || ''}
`; } // ========================================================== // 12. 导出 // ========================================================== module.exports = { BRAND, esc, fmtLikes, renderVocCard, renderSectionHead, renderStatStrip, renderDecisionList, renderInsightHero, renderCompareTable, renderMatrix2x2, renderTheoryBox, renderFindingCard, };