// ==============================================================================
// 江中肝纯片 · VOC 报告共享 UI 组件(复刻 houguyin-components.js,换 LIVER_BRAND + 补 amazon 平台)
// ==============================================================================
// 组件库内部默认使用 LIVER_BRAND,所有 render* 函数都接受 accent 参数覆盖
// 设计:不依赖外部图表库,纯 CSS + SVG 轻量可视化
// ----------------------------------------------------------------
// LIVER_BRAND · 肝纯片专属色板
// ----------------------------------------------------------------
const BRAND = {
JIANG_GREEN: '#2D9C5D', // 江中品牌主色(品牌/正向)
LIVER_AMBER: '#D4A574', // 肝纯片 · 琥珀色(产品主色)
TENGCHA_GREEN: '#4A8A3A', // 藤茶深绿(DMY 原料章节)
DEEP_AMBER: '#B87333', // 深琥珀(礼品/传统)
CREAM: '#FAE8D4', // 奶油米色(包装底色)
TECH_BLUE: '#0085CA', // 科技蓝(数据/理论/权威)
WARM_ORANGE: '#FF8C42', // 场景地图橙
CORAL: '#FF6B6B', // 痛点/诘问/差评情绪
SAGE: '#87A96B', // 药食同源绿(日常养护)
PURPLE: '#8B5CF6', // KANO × JTBD 理论
ROSE: '#FF4D8D', // 竞品对比玫瑰
WINE_RED: '#8B2942', // 应酬解酒/酒饮场景
GOLD: '#C8A04B', // 国礼级奢华金
};
const PLATFORM_LABELS = {
xhs: { name: '小红书', color: '#FF2442', short: '红' },
douyin: { name: '抖音', color: '#1A1A1A', short: '抖' },
amazon: { name: 'Amazon', color: '#FF9900', short: 'Az' },
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 真正讨论护肝/保健品
// ================================================================
const LIVER_CONTEXT_RE = /肝|护肝|养肝|保肝|伤肝|转氨酶|脂肪肝|解酒|宿醉|保健品|补充剂|水飞蓟|藤茶|DMY|肝片|肝纯|肝功|奶蓟|护肝片|熬夜.*肝|酒.*肝|liver|supplement|hangover|milk.?thistle|silymarin|dihydromyricetin/i;
// ================================================================
// 英文检测 + 翻译系统(Amazon 护肝品评论)
// ================================================================
function isEnglish(text) {
const t = String(text || '');
if (t.length < 10) return false;
const asciiLetters = t.replace(/[^a-zA-Z]/g, '').length;
return asciiLetters / t.length > 0.45;
}
// 翻译查找表:key = content 前50字符(去空格小写)
const EN_TRANSLATIONS = {
// Ch1.2 positive
'nogimmicks,nononsense,itseriouslyworks': '真的有效,不是噱头。我有焦虑症,宿醉会引发恐慌发作——恶心、胃不舒服全会加重。用了 Cheers Restore 这些问题都消失了。不是万能药,但比不吃好 80%。药片稍微有点大。',
'i\'vetriedafewdifferenthangovercuresandt': '试过好几种解酒产品,这个目前效果最好、最明显。之前用 Pre-Alcohol 系列觉得有点用,但这个更强。打游戏喝多了的完美解药,方便好用,还含护肝成分。现在是我的首选——比之前用的还便宜!',
// Ch1.2 negative
'thecheersprotectdailyliversupportsupple': 'Cheers Protect 日常护肝补充剂让我血压飙升得很厉害。',
'althoughididnotopenthepackage,it\'sobvio': '虽然没开封,但明显有几颗胶囊在运输中破损了。不能退货!想换一批也没有这个选项。',
// Ch1.3 milk thistle / silymarin
'decidedtotrythisoneinsteadofaplainmilkt': '想试试这个替代纯水飞蓟产品。说不好有没有用,但 4 周胖了 5 磅。饮食和运动习惯都没变,唯一区别就是加了这个补充剂。喜欢 Now 品牌,但不会再买这款。',
'checksuppcoapptoeval': '可以用 suppco app 评估保健品。这家公司辅料成分安全,但蒲公英根和水飞蓟的功效没有评测数据。建议看看 Thorne 品牌。',
'don\'tbefooledbythe7500mgclaim': '别被 7500mg 的宣传忽悠了——这是在误导消费者!标签写"7500mg 含量",让人以为每粒有 7500mg 水飞蓟粉。实际每粒只含约 250mg 水飞蓟提取物(30:1 浓缩),所谓 7500mg 是"草本等效"换算。胶囊实际总重仅约 715mg(含大量填充物)。大多数消费者不懂这个区别。另外还有质量问题:部分药片是碎的。',
// Ch1.3 silymarin (additional)
'ivebeentakingthisbronsonmilkthistlewithda': '一直在服用 Bronson 水飞蓟+蒲公英根补充剂。我喜欢它把水飞蓟素和蒲公英根结合在一起,都是常见的护肝抗氧化成分。配方简洁,成分清晰。胶囊大小标准,一瓶 360 粒方便长期服用。不含 GMO/麸质/大豆,美国 cGMP 标准生产。服用期间没有不适,效果虽然不明显,但作为日常保健可以接受。总体是一个老牌子的靠谱选择。',
// Ch2.1 水飞蓟 competitor evidence
'gotmyannualbloodworklastsummermyaltlevel': '去年夏天年度体检,ALT 指标飙升(76→69),正常值上限约 44。最近两三年 ALT 一直偏高但还在正常范围。医生怀疑胆结石,超声检查正常。朋友推荐水飞蓟,2025年8月开始服用,复查血液指标明显改善。',
'beentakingthisdailyforaboutamonthandahal': '每天服用约一个半月,作为护肝日常方案。配方不错——水飞蓟提取物标准化到 80% 水飞蓟素含量,加上朝鲜蓟和蒲公英辅助。胶囊易吞咽,无胃部不适。NOW Foods 品控口碑好,这款也不例外。',
// Ch4.2 fatty liver
'iwashesitanttopurchasethisbecauseihaveb': '一开始犹豫要不要买——之前买了很多补充剂都没用,还用过 Ozempic 减肥反弹了。一直感觉疲惫无力。想起 2007 年做过的护肝减重法,就搜了这个产品。每天喝两次,味道不错,21 天不做其他改变就瘦了 16 磅,感觉好多了,浮肿消了,胃部压力也没了。我戒断恢复 5 年半一直觉得身体不好,现在终于感觉正常健康了。强烈推荐!',
// Common DHM / hangover reviews
'thesearealifesaver!firsttimeusingthesea': '这是救命神器!第一次用是朋友给的,我当时半信半疑,但喝多了第二天真的管用。',
'honestlyiwentinthinkingt': '说实话一开始觉得是浪费钱,但确实有惊喜。睡前吃几粒,第二天感觉好很多。',
'thisstuffdefinitelyhelpsmeavoidhangover': '这个确实帮我避免了宿醉,以前一直有这个问题。',
'iwasskepticalatfirstbutaftermyfirstuse': '一开始半信半疑,但第一次用后就信了。喝了不少酒第二天居然没事。',
'worksgreatforhangovers': '解酒效果很好,',
'greatsupplementforliversupport': '很好的护肝补充剂,',
'didnothelpwithmyhangoveratall': '对我的宿醉完全没用。',
'noticeabledifferenceinhowafeelafterdrin': '喝酒后感觉明显不同。',
'mademedizzyandnauseous': '让我头晕恶心。',
'goodqualityproduct,fastshipping': '产品质量好,发货快。',
'tasteisawful,hardtoswallow': '味道很差,难以下咽。',
'besthangoversupplementi\'veeverused': '用过的最好的解酒补充剂。',
'completewasteofmoney': '完全浪费钱。',
'helpswithliverdetox': '有助于肝脏排毒。',
};
function getTranslation(content) {
const norm = s => String(s || '').slice(0, 50).replace(/[\s,.!?;:'"()\-]/g, '').toLowerCase();
const key = norm(content);
if (!key || key.length < 8) return null;
for (const [k, v] of Object.entries(EN_TRANSLATIONS)) {
const nk = norm(k);
if (key.startsWith(nk.slice(0, 25)) || nk.startsWith(key.slice(0, 25))) return v;
}
return null;
}
// ================================================================
// VOC 证据卡(核心组件)· 支持 XHS/抖音/Amazon 三平台
// ================================================================
function renderVocCard(item, opts = {}) {
const { accent = BRAND.LIVER_AMBER, 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) : [];
const isAmz = item.platform === 'amazon';
// Amazon 特殊:展示 rating + title
const amzHeader = isAmz && item.rating != null
? `
${
Array.from({ length: 5 }).map((_, i) => `★`).join('')
}${item.verified ? `已购` : ''}
`
: '';
const amzTitle = isAmz && item.title
? `${esc(item.title)}
`
: '';
return `
${pf.short}
${esc(item.nickname || '匿名用户')}
${item.ip ? `· ${esc(item.ip)}` : ''}
${item.keyword ? `· ${esc(item.keyword)}` : ''}
${sen.icon} ${sen.label}
${isAmz ? '👍' : '♥'}${fmtLikes(item.likes)}
${isSeed ? `◈模式` : `✓真实`}
${amzHeader}
${amzTitle}
${(() => {
const content = truncate(item.content, maxChars);
if (isEnglish(content)) {
const trans = getTranslation(item.content);
if (trans) {
return `
"${esc(trans)}"
原文: "${esc(truncate(content, 120))}"
🔄 已翻译`;
} else {
return `
"${esc(content)}"
EN 英文原评`;
}
}
return `
"${esc(content)}"
`;
})()}
${tags.length ? `
${tags.map((t) => `${esc(t)}`).join('')}
` : ''}
${showBasis && item.basis ? `
依据:${esc(item.basis)}
` : ''}
`;
}
// ================================================================
// VOC 网格(多条)
// ================================================================
function renderVocGrid(items, opts = {}) {
const { columns = 2, accent = BRAND.LIVER_AMBER, compact = false, maxChars } = opts;
return `
${items.map((it) => renderVocCard(it, { accent, compact, maxChars })).join('')}
`;
}
// ================================================================
// Insight Hero · 大洞察摘要条
// ================================================================
function renderInsightHero({ eyebrow, title, subtitle, color = BRAND.LIVER_AMBER }) {
return `
${eyebrow ? `
${esc(eyebrow)}
` : ''}
${title}
${subtitle ? `
${subtitle}
` : ''}
`;
}
// ================================================================
// 决策项列表(Takeaways)
// ================================================================
function renderDecisionList(items, opts = {}) {
const { accent = BRAND.JIANG_GREEN, title = 'DECISION · 落地决策项', columns = 2 } = opts;
return `
${esc(title)}
${items.map((it, i) => `
`).join('')}
`;
}
// ================================================================
// 理论映射盒(KANO/JTBD/场景三元素)
// ================================================================
function renderTheoryBox({ theory, mapping, accent = BRAND.PURPLE }) {
return `
THEORY · ${esc(theory)}
${mapping}
`;
}
// ================================================================
// Tag 频次云(用于某假设的主题分布)
// ================================================================
function renderTagCloud(tagGroups, opts = {}) {
const { accent = BRAND.LIVER_AMBER, 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.LIVER_AMBER } = opts;
return `
${stats.map((s) => `
${s.num}
${esc(s.label)}
${s.delta ? `
${esc(s.delta)}
` : ''}
`).join('')}
`;
}
// ================================================================
// 水平条形对比图(SVG)
// ================================================================
function renderBarChart(rows, opts = {}) {
const { accent = BRAND.LIVER_AMBER, 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)}
` : ''}
`;
}
// ================================================================
// 2×2 矩阵图(四象限)
// ================================================================
function renderMatrix2x2({ xLabel, yLabel, xLow, xHigh, yLow, yHigh, quadrants, accent = BRAND.LIVER_AMBER }) {
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)}
`;
}
// ================================================================
// 章节 section head(标题区通用)
// ================================================================
function renderSectionHead({ eyebrow, title, subtitle, color = BRAND.LIVER_AMBER }) {
return `
${esc(eyebrow)}
${title}
${subtitle ? `
${subtitle}
` : ''}
`;
}
// ================================================================
// 对比表格(Pro vs Con / 本品 vs 竞品)
// ================================================================
function renderCompareTable({ title, headers, rows, accent = BRAND.LIVER_AMBER }) {
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('')}
`;
}
// ================================================================
// 洞察卡片(纯文字 Finding)
// ================================================================
function renderFindingCard({ icon = '💡', title, body, accent = BRAND.LIVER_AMBER }) {
return ``;
}
// ================================================================
// 假设标签(H1-H8 glyph)
// ================================================================
function renderHypothesisBadge(h, opts = {}) {
const { accent = BRAND.LIVER_AMBER, 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,
LIVER_CONTEXT_RE,
esc,
fmtLikes,
truncate,
isEnglish,
getTranslation,
renderVocCard,
renderVocGrid,
renderInsightHero,
renderDecisionList,
renderTheoryBox,
renderTagCloud,
renderStatStrip,
renderBarChart,
renderMatrix2x2,
renderSectionHead,
renderCompareTable,
renderFindingCard,
renderHypothesisBadge,
};