// ==============================================================================
// 江中乳酸菌素片儿童版 · VOC 报告共享 UI 组件(复刻 liver-components.js,换 LACTIC_BRAND)
// ==============================================================================
// ----------------------------------------------------------------
// LACTIC_BRAND · 乳酸菌素片儿童版专属色板(儿童友好 + 江中品牌主色)
// ----------------------------------------------------------------
const BRAND = {
JIANG_GREEN: '#2D9C5D', // 江中品牌主色
LACTIC_MINT: '#7ED4C2', // 乳酸菌清爽薄荷(产品主色·清洁感)
BERRY_PINK: '#FF8BA7', // 莓果粉(儿童可爱)
SKY_BLUE: '#6EC1E4', // 晴空蓝(安全信任)
SUNNY_YELLOW: '#FFD166', // 鹅黄(儿童活力)
CREAM: '#FFF4E6', // 奶油米色
CORAL: '#FF6B6B', // 痛点/诘问/差评情绪
WARM_ORANGE: '#FF8C42', // 温暖橙(场景地图)
TECH_BLUE: '#0085CA', // OTC 专业蓝(药品信任)
SAGE: '#87A96B', // 药食同源绿
PURPLE: '#8B5CF6', // KANO × JTBD 理论
ROSE: '#FF4D8D', // 竞品对比玫瑰
WINE_RED: '#8B2942', // 警示红
GOLD: '#C8A04B', // 礼品奢华金
BABY_PEACH: '#FFCBA4', // 婴儿桃(0-3 岁人群)
PRESCHOOL_LIME: '#C5E063', // 学龄前青柠(3-6 岁)
SCHOOL_LAVENDER: '#B4A7D6', // 学龄期薰衣草(6-12 岁)
};
const PLATFORM_LABELS = {
xhs: { name: '小红书', color: '#FF2442', short: '红' },
douyin: { name: '抖音', color: '#1A1A1A', short: '抖' },
taobao: { name: '淘宝', color: '#FF5000', short: '淘' },
jd: { name: '京东', color: '#E1251B', short: '京' },
tmall: { name: '天猫', color: '#FF0036', short: '猫' },
zhihu: { name: '知乎', color: '#0084FF', short: '知' },
unknown: { name: '其他', color: '#888', short: '—' },
};
const SENTIMENT_LABELS = {
positive: { label: '好评', color: '#2D9C5D', icon: '▲' },
neutral: { label: '中性', color: '#888', icon: '●' },
negative: { label: '差评', color: '#FF6B6B', icon: '▼' },
conflicted:{ label: '矛盾', color: '#FF8C42', icon: '◆' },
};
function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
}
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);
}
function truncate(s, n = 160) {
s = String(s ?? '');
return s.length > n ? s.slice(0, n - 1) + '…' : s;
}
// ================================================================
// VOC 证据卡(核心组件)
// ================================================================
function renderVocCard(item, opts = {}) {
const { accent = BRAND.LACTIC_MINT, showBasis = false, compact = false, maxChars = 240 } = opts;
const pf = PLATFORM_LABELS[item.platform] || PLATFORM_LABELS.unknown;
const sen = SENTIMENT_LABELS[item.sentiment] || SENTIMENT_LABELS.neutral;
const isSeed = (item.source || '').includes('pattern') || (item.source || '').includes('seed');
const tags = Array.isArray(item.tags) ? item.tags.slice(0, 3) : [];
return `
${pf.short}
${esc(item.nickname || '匿名用户')}
${item.ip ? `· ${esc(item.ip)}` : ''}
${item.keyword ? `· ${esc(item.keyword)}` : ''}
${sen.icon} ${sen.label}
♥${fmtLikes(item.likes)}
${isSeed ? `◈模式` : `✓真实`}
"${esc(truncate(item.content, maxChars))}"
${tags.length ? `
${tags.map((t) => `${esc(t)}`).join('')}
` : ''}
${showBasis && item.basis ? `
依据:${esc(item.basis)}
` : ''}
`;
}
function renderVocGrid(items, opts = {}) {
const { columns = 2, accent = BRAND.LACTIC_MINT, compact = false, maxChars } = opts;
return `
${items.map((it) => renderVocCard(it, { accent, compact, maxChars })).join('')}
`;
}
function renderInsightHero({ eyebrow, title, subtitle, color = BRAND.LACTIC_MINT }) {
return `
${eyebrow ? `
${esc(eyebrow)}
` : ''}
${title}
${subtitle ? `
${subtitle}
` : ''}
`;
}
function renderDecisionList(items, opts = {}) {
const { accent = BRAND.JIANG_GREEN, title = 'DECISION · 落地决策项', columns = 2 } = opts;
return `
${esc(title)}
${items.map((it, i) => `
`).join('')}
`;
}
function renderTheoryBox({ theory, mapping, accent = BRAND.PURPLE }) {
return `
THEORY · ${esc(theory)}
${mapping}
`;
}
function renderTagCloud(tagGroups, opts = {}) {
const { accent = BRAND.LACTIC_MINT, top = 15, title = '主题分布' } = opts;
const list = tagGroups.slice(0, top);
if (!list.length) return '';
const maxCount = Math.max(...list.map((g) => g.count));
return `
${esc(title)} · TOP ${list.length}
${list.map((g) => {
const ratio = g.count / maxCount;
const fs = (0.72 + ratio * 0.45).toFixed(2);
const bg = Math.round(10 + ratio * 30);
return `${esc(g.tag)} ×${g.count}`;
}).join('')}
`;
}
function renderStatStrip(stats, opts = {}) {
const { accent = BRAND.LACTIC_MINT } = opts;
return `
${stats.map((s) => `
${s.num}
${esc(s.label)}
${s.delta ? `
${esc(s.delta)}
` : ''}
`).join('')}
`;
}
function renderBarChart(rows, opts = {}) {
const { accent = BRAND.LACTIC_MINT, height = 260, title = '' } = opts;
const max = Math.max(...rows.map((r) => r.value), 1);
const barH = 28;
const gap = 10;
const padL = 160;
const padR = 80;
const padV = 16;
const totalH = rows.length * (barH + gap) + padV * 2;
const W = 900;
return `
${title ? `
${esc(title)}
` : ''}
`;
}
function renderMatrix2x2({ xLabel, yLabel, xLow, xHigh, yLow, yHigh, quadrants, accent = BRAND.LACTIC_MINT }) {
const quadStyle = (q) => ({
color: q?.color || accent,
title: q?.title || '',
note: q?.note || '',
items: q?.items || [],
});
const q1 = quadStyle(quadrants.q1);
const q2 = quadStyle(quadrants.q2);
const q3 = quadStyle(quadrants.q3);
const q4 = quadStyle(quadrants.q4);
const renderQuad = (q) => `
${esc(q.title)}
${q.items.map((it) => `- ${it}
`).join('')}
${q.note ? `
${q.note}
` : ''}
`;
return `
${esc(yLabel)}
${esc(yHigh)}
${esc(yHigh)}
${renderQuad(q2)}
${renderQuad(q1)}
${renderQuad(q3)}
${renderQuad(q4)}
${esc(xLow)} ← ${esc(xLabel)} → ${esc(xHigh)}
`;
}
function renderSectionHead({ eyebrow, title, subtitle, color = BRAND.LACTIC_MINT }) {
return `
${esc(eyebrow)}
${title}
${subtitle ? `
${subtitle}
` : ''}
`;
}
function renderCompareTable({ title, headers, rows, accent = BRAND.LACTIC_MINT }) {
return `
${title ? `
${esc(title)}
` : ''}
${headers.map((h, i) => `| ${esc(h)} | `).join('')}
${rows.map((r, ri) => `
${r.map((cell, ci) => {
const isHeader = ci === 0;
const content = typeof cell === 'string' ? cell : (cell.html || '');
const cellColor = typeof cell === 'object' && cell.color ? cell.color : (isHeader ? 'var(--text-2)' : 'var(--text-1)');
const weight = typeof cell === 'object' && cell.bold ? '700' : '400';
return `| ${content} | `;
}).join('')}
`).join('')}
`;
}
function renderFindingCard({ icon = '💡', title, body, accent = BRAND.LACTIC_MINT }) {
return ``;
}
function renderHypothesisBadge(h, opts = {}) {
const { accent = BRAND.LACTIC_MINT, size = 'md' } = opts;
const fs = size === 'sm' ? '0.64rem' : size === 'lg' ? '0.82rem' : '0.72rem';
const px = size === 'sm' ? '3px 8px' : size === 'lg' ? '6px 14px' : '4px 10px';
return `${esc(h)}`;
}
module.exports = {
BRAND,
PLATFORM_LABELS,
SENTIMENT_LABELS,
esc,
fmtLikes,
truncate,
renderVocCard,
renderVocGrid,
renderInsightHero,
renderDecisionList,
renderTheoryBox,
renderTagCloud,
renderStatStrip,
renderBarChart,
renderMatrix2x2,
renderSectionHead,
renderCompareTable,
renderFindingCard,
renderHypothesisBadge,
};