Files
mama-box/topic/_shared/article.js

228 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 孕期专题页共用渲染(2026-11-01)
* 用户:「其他妈妈也在搜」剩下的问题都做成专题页,UI 和文字参考「叶酸、DHA、铁、钙要不要补」(topic/buji)。
* 每个专题目录只放 index.html(内联 window.TOPIC 数据)+ img/hero.webp,版式全在这里和 article.css。
* 区块 type:cards 卡片矩阵 / timeline 按孕周横向条 / vs 两栏对照 / list 清单 / steps 步骤 / doc 找医生 / check 自查。
* 自成一页:不引 app.js;返回回 ../../;工具跳转走 ../_shared/tools.js(同源路径 + search + hash)。
*/
(function () {
/* 11-09 小程序「复制链接」直达:加载 js/host-nav.js(容器页返回走 navigateBack、页内链接开容器页) */
try { if (!window.MBHost && document.currentScript) { var hs = document.createElement('script'); hs.src = new URL('../../js/host-nav.js?v=26110908', document.currentScript.src).href; document.head.appendChild(hs); } } catch (e) {}
'use strict';
var D = window.TOPIC;
if (!D) return;
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return [].slice.call((r || document).querySelectorAll(s)); };
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
/* 只放行 <em>:答案里用它标重点,其余一律转义 */
function em(s) { return esc(s).replace(/&lt;(\/?)em&gt;/g, '<$1em>'); }
function hp(n) { var h = (location.hash.slice(1) || '') + '&' + (location.search.slice(1) || ''); var m = h.match(new RegExp('(?:^|&)' + n + '=([^&]*)')); return m ? decodeURIComponent(m[1]) : ''; }
function ic(id) { return '<svg class="ic"><use href="#i-' + id + '" xlink:href="#i-' + id + '"/></svg>'; }
/* 主色 */
var C = D.color || ['#2E7CF6', '46,124,246', '#EAF2FF'];
var root = document.documentElement.style;
root.setProperty('--c', C[0]); root.setProperty('--cr', C[1]); root.setProperty('--cb', C[2]);
var SPRITE = '<svg class="hide" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">' +
'<symbol id="i-back" viewBox="0 0 24 24"><path d="M15 5l-7 7 7 7"/></symbol>' +
'<symbol id="i-down" viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></symbol>' +
'<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/></symbol>' +
'<symbol id="i-doc" viewBox="0 0 24 24"><path d="M12 21s-7-4.4-7-10.2A4.3 4.3 0 0 1 12 8a4.3 4.3 0 0 1 7 2.8C19 16.6 12 21 12 21z"/><path d="M12 11v4M10 13h4"/></symbol>' +
'</svg>';
function title() {
var out = '';
(D.title || [D.q]).forEach(function (p, i) {
if (p === '\n') out += '<br>';
else out += i === 2 ? '<span class="hl">' + esc(p) + '</span>' : esc(p);
});
return out;
}
function sh(s, i) {
return '<div class="sh"><span class="no num">' + (i < 9 ? '0' : '') + (i + 1) + '</span><h2>' + esc(s.title) + '</h2>' +
(s.en ? '<span class="en">' + esc(s.en) + '</span>' : '') + '</div>' + (s.key ? '<p class="key">' + esc(s.key) + '</p>' : '');
}
var R = {
cards: function (s) {
var h = '<div class="mx">';
(s.items || []).forEach(function (x) {
h += '<div class="nu fi' + (x.must ? ' must' : '') + '"><div class="nu-h"><b>' + esc(x.name) + '</b>' + (x.verdict ? '<span class="vd">' + esc(x.verdict) + '</span>' : '') + '</div>' +
(x.label ? '<span class="lb">' + esc(x.label) + '</span>' : '') +
'<span class="amt num">' + esc(x.amt) + (x.unit ? '<small>' + esc(x.unit) + '</small>' : '') + '</span>' +
(x.when ? '<div class="when">' + ic('clock') + '<span>' + esc(x.when) + '</span></div>' : '') + '</div>';
});
h += '</div>';
if (s.why) h += '<details class="why"><summary>' + esc(s.why.q) + ic('down') + '</summary><p>' + esc(s.why.a) + '</p></details>';
return h;
},
timeline: function (s, i) {
var h = '<div class="me" data-me></div><div class="gt" data-gt><div class="col-hl"></div><div class="gr hd"><span></span>';
(s.cols || []).forEach(function (c, k) { h += '<span data-c="' + k + '">' + esc(c) + '</span>'; });
h += '</div>';
(s.rows || []).forEach(function (r) {
h += '<div class="gr fi"><span class="nm">' + esc(r.name) + '</span><div class="bars">';
(r.bars || []).forEach(function (b) { h += '<i class="' + esc(b.cls || '') + (b.span === 2 ? ' s2' : b.span === 3 ? ' s3' : b.span === 4 ? ' s4' : '') + '">' + esc(b.t) + '</i>'; });
h += '</div></div>';
});
h += '</div><div class="legend">';
(s.legend || [['', '重点'], ['lt', '日常'], ['dash', '听医生']]).forEach(function (l) { h += '<span><i class="' + esc(l[0]) + '"></i>' + esc(l[1]) + '</span>'; });
return h + '</div>';
},
vs: function (s) {
var hd = s.heads || ['', ''];
var h = '<div class="vs"><div class="vs-h"><span></span><span>' + esc(hd[0]) + '</span><span>' + esc(hd[1]) + '</span></div>';
(s.rows || []).forEach(function (r) {
h += '<div class="vr fi"><span class="nm">' + esc(r.name) + '</span><span class="cl' + (r.win === 0 ? ' win' : '') + '">' + esc(r.a) + '</span><span class="cl' + (r.win === 1 ? ' win' : '') + '">' + esc(r.b) + '</span></div>';
});
h += '</div>';
if (s.note) h += '<p class="vs-note">' + esc(s.note) + '</p>';
return h;
},
list: function (s) {
var h = '<ul class="ls">';
(s.items || []).forEach(function (x) { h += '<li class="fi ' + esc(x.tone || 'ok') + '"><i>' + esc(x.tag) + '</i><div><b>' + esc(x.b) + '</b>' + (x.s ? '<span>' + esc(x.s) + '</span>' : '') + '</div></li>'; });
return h + '</ul>';
},
steps: function (s) {
var h = '<ol class="st">';
(s.items || []).forEach(function (x) { h += '<li class="fi"><b>' + esc(x.b) + '</b>' + (x.s ? '<span>' + esc(x.s) + '</span>' : '') + '</li>'; });
return h + '</ol>';
},
doc: function (s) {
var h = '<div class="doc"><span class="di">' + ic('doc') + '</span><div><b>' + esc(s.b) + '</b><span>' + esc(s.s) + '</span></div></div><div class="tags">';
(s.tags || []).forEach(function (t) { h += '<span>' + esc(t) + '</span>'; });
return h + '</div>';
},
check: function (s, i) {
var h = '<div class="tool" data-check="' + i + '">';
(s.qs || []).forEach(function (q, k) {
h += '<div class="q" data-q="' + k + '"><div class="q-t"><span class="num">Q' + (k + 1) + '</span>' + esc(q.q) + '</div><div class="opts">';
(q.opts || []).forEach(function (o, j) { h += '<button class="opt" data-v="' + j + '">' + esc(o.t) + '</button>'; });
h += '</div></div>';
});
return h + '<p class="hint">' + esc(s.hint || ((s.qs || []).length + ' 题选完自动出结果,只在本机计算')) + '</p>' +
'<div class="res"><h3>给你的建议</h3><ul></ul><p class="res-note">仅供参考,具体以产检医生意见为准。</p></div></div>';
}
};
var h = SPRITE + '<div id="scroll"><header class="bar" id="bar"><button class="back" id="back" aria-label="返回">' + ic('back') + '</button><span class="t">孕期专题</span></header>' +
'<div class="top"><section class="hero"><span class="eyebrow">' + esc(D.eyebrow) + '</span><h1>' + title() + '</h1>' +
(D.sub ? '<p class="sub">' + esc(D.sub) + '</p>' : '') + '</section>' +
'<div class="pic"><img src="img/hero.webp" alt="' + esc(D.heroAlt || '') + '"></div></div>' +
'<div class="answer"><span class="pill">一句话答案</span><p>' + em(D.answer) + '</p></div>';
if (D.three && D.three.length) {
h += '<section class="three fade"><h2>记住这 ' + D.three.length + ' 点</h2><ol class="nc">';
D.three.forEach(function (x, i) { h += '<li class="fi"><span class="n num">' + (i + 1) + '</span><div><b>' + esc(x.b) + '</b><span>' + esc(x.s) + '</span></div></li>'; });
h += '</ol></section>';
}
(D.sections || []).forEach(function (s, i) {
if (!R[s.type]) return;
h += '<section class="blk' + (s.tint ? ' tint' : '') + ' fade" id="s' + (i + 1) + '">' + sh(s, i) + R[s.type](s, i) + '</section>';
});
h += '<div id="tools" class="fade"></div>';
if (D.sources && D.sources.length) {
h += '<details class="src"><summary>参考来源' + ic('down') + '</summary><ul>';
D.sources.forEach(function (x) { h += '<li>' + esc(x) + '</li>'; });
h += '</ul></details>';
}
h += '<p class="foot">本页为科普参考,不能代替医生诊断。</p>' +
'<div class="tp-brand"><img src="../../assets/img/brand-logo-c.png" alt="兜爸心选"><i></i><span>孕育知识整理</span></div></div>'; /* 11-02 品牌落款(样式在 article.css) */
document.body.insertAdjacentHTML('afterbegin', h);
document.title = D.q || document.title;
var sc = $('#scroll');
$('#back').addEventListener('click', function () {
if (window.MBHost && MBHost.back(function () { location.href = '../../' + location.search + location.hash; })) return; /* 11-09 容器页:回小程序上一页 */
if (document.referrer && document.referrer.indexOf(location.host) >= 0 && history.length > 1) history.back();
else location.href = '../../' + location.search + location.hash;
});
sc.addEventListener('scroll', function () { $('#bar').classList.toggle('on', sc.scrollTop > 8); }, { passive: true });
function goTo(el) {
var top = el.getBoundingClientRect().top - sc.getBoundingClientRect().top + sc.scrollTop - 60;
sc.scrollTo({ top: top, behavior: 'smooth' });
}
/* 推荐工具 */
function renderTools(wk) {
if (!window.TopicTools || !D.tools) return;
var el = $('#tools'); el.className = 'fade in';
TopicTools.render(el, D.tools.items || [], { title: D.tools.title || '用得上的工具', color: C[0], bg: C[2], wk: wk || 0 });
}
renderTools(0);
/* 自查:每题选一个,全部选完按 a→t→k 排出最多 3 条 */
var TAG = { a: '问医生', t: '加一点', k: '保持' }, ORD = { a: 0, t: 1, k: 2 };
$$('[data-check]').forEach(function (box) {
var s = D.sections[+box.dataset.check], ans = {};
$$('.q', box).forEach(function (qb) {
$$('.opt', qb).forEach(function (o) {
o.addEventListener('click', function () {
$$('.opt', qb).forEach(function (x) { x.classList.toggle('on', x === o); });
ans[qb.dataset.q] = +o.dataset.v;
var res = $('.res', box), first = !res.classList.contains('on');
if (Object.keys(ans).length < s.qs.length) return;
var L = s.qs.map(function (q, k) { return q.opts[ans[k]].adv; }).filter(Boolean);
L.sort(function (x, y) { return (ORD[x.tone] || 0) - (ORD[y.tone] || 0); });
var ul = $('ul', res); ul.innerHTML = '';
L.slice(0, 3).forEach(function (it) {
var li = document.createElement('li'); li.className = it.tone;
li.innerHTML = '<i></i><div><b></b><span></span></div>';
li.querySelector('i').textContent = TAG[it.tone] || '';
li.querySelector('b').textContent = it.b; li.querySelector('span').textContent = it.s || '';
ul.appendChild(li);
});
$('.hint', box).style.display = 'none';
res.classList.add('on');
if (first) setTimeout(function () { goTo(res); }, 60);
});
});
});
});
/* 按孕周点亮 timeline 那一列 */
function markWeek(w, post) {
(D.sections || []).forEach(function (s, i) {
if (s.type !== 'timeline' || !s.weeks) return;
var col = -1;
s.weeks.forEach(function (r, k) { if (r && !post && w >= r[0] && w < r[1]) col = k; });
if (post) s.cols.forEach(function (c, k) { if (/产后|哺乳|月子|出生/.test(c)) col = k; });
if (col < 0) return;
var sec = $('#s' + (i + 1)), hd = $('.gr.hd [data-c="' + col + '"]', sec), gt = $('[data-gt]', sec), box = $('.col-hl', sec);
function place() {
var a = hd.getBoundingClientRect(), g = gt.getBoundingClientRect();
box.style.left = (a.left - g.left - 1) + 'px'; box.style.width = (a.width + 2) + 'px'; box.classList.add('on');
}
place(); window.addEventListener('resize', place);
hd.style.color = C[0];
if (s.tips && s.tips[col]) { var me = $('[data-me]', sec); me.textContent = (post ? '宝宝出生了' : '你在孕 ' + w + ' 周') + ':' + s.tips[col]; me.classList.add('on'); }
});
}
var openid = hp('openid');
if (openid) {
fetch('/api/mp/public/mama-box/pregnancy?openid=' + encodeURIComponent(openid))
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (j) {
var p = j && j.code === 200 && j.result; if (!p) return;
if (p.postpartum) markWeek(0, true);
else if (p.week != null && !p.empty) { markWeek(p.week, false); renderTools(p.week); }
}).catch(function () {});
}
/* 进场 fade-up */
$$('.fade').forEach(function (el) { $$('.fi', el).forEach(function (c, i) { c.style.transitionDelay = (i * 60 + 80) + 'ms'; }); });
if ('IntersectionObserver' in window) {
var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { root: sc, threshold: .08 });
$$('.fade').forEach(function (el) { io.observe(el); });
} else $$('.fade').forEach(function (el) { el.classList.add('in'); });
/* 禁止下拉回弹 */
var y0 = 0;
sc.addEventListener('touchstart', function (e) { y0 = e.touches[0].clientY; }, { passive: true });
sc.addEventListener('touchmove', function (e) {
var dy = e.touches[0].clientY - y0;
if ((sc.scrollTop <= 0 && dy > 0) || (sc.scrollTop + sc.clientHeight >= sc.scrollHeight - 1 && dy < 0)) e.preventDefault();
}, { passive: false });
document.addEventListener('touchmove', function (e) { if (!sc.contains(e.target)) e.preventDefault(); }, { passive: false });
})();