Files
mama-box/topic/huafei/index.html

1441 lines
111 KiB
HTML
Raw Permalink 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<meta name="format-detection" content="telephone=no">
<title>生娃计算器 · 养一个孩子到底要花多少钱</title>
<!-- 孕期专题 · 生娃计算器(孕期首页「其他妈妈也在搜」第 1 条入口)。主色 #18A058,浅底 #EAF7F0。
2026-11-01 v5(在 v4 三条路线 + 养娃账单小票上继续改):
① 首屏大标题「生娃计算器」+ 副标题;三张路线卡带 01/02/03 序号;结果揭晓前全程不出现任何金额;
② 问题(按所选阶段出现):算哪一段(多选,默认全部)→ 城市 → 头胎二胎 → 0~3 岁谁带 → 上学路线 → 课外班 → 家庭月收入(可跳过);
③ 账单只列所选阶段,每段 12~24 项明细,单项取中间值、小计合计能加对;1/2/3 岁累计只在含 0~1 / 1~3 岁时显示;
④ 填了收入:账单里给「≈ 你家 X 个月 / X 年收入」+ 所选阶段平均每月占收入比例条;收入只存本机;
⑤ 推荐工具、打印动画同 v4(收尾卡、复制、生成卡片 v7 已删);
⑥ v6(11-01):账单下加「保存账单图片」,canvas 把整张小票画成长图(宽 750,高按内容,超 15800px 先压行高再整体缩小),复用卡片弹层、长图可滚动。改价格只改脚本里的 COST 表。
⑦ v7(2026-11-01,用户:题少了加几道、去掉男女题、给妈妈的话只留提示):
· 去掉「你是女生/男生」题及所有按性别换说法的文案;
· 新增 4 题并接进计算:在哪类医院(公立普通/公立特需/私立 → 产检、B 超、分娩、病房、筛查、住院餐、生育保险报销)、
顺产还是剖宫产(顺产/剖宫产/还不确定 → 分娩费、无痛或镇痛泵、报销额)、月子打算(家人/月嫂/月子中心 → 月子主项、月子餐食材)、
喂养方式(母乳为主/混合/奶粉 → 0~1 岁奶粉量、1~3 岁奶粉、吸奶器/催乳/哺乳内衣);医院、月子跟路线默认,分娩、喂养默认「还不确定」「混合」;
· 现在的题序:算哪一段 → 城市 → 头胎二胎 → 医院 → 顺产剖宫产 → 月子 → 喂养 → 0~3 岁谁带 → 上学路线 → 课外班 → 家庭月收入(按所选阶段出现);
· 「写在账单后面 / 给妈妈的话」整块删掉(换一句、复制发给妈妈、生成卡片、drawCard 都删了),只留一行浅色小提示;卡片弹层只给「保存账单图片」用。
⑧ v8(2026-11-04,用户:题太少,加到 12 道以上、不超过 15 道):
· 新增 4 题,档次跟答案走(G() 项,不再跟路线):唐筛还是无创 DNA(screen → 筛查项)、满月百天要不要办(party → 孕妇照、报喜喜糖、新生儿摄影、满月酒、百天照、百天宴)、
宝宝用品怎么置办(gear → 待产包、推车、婴儿床、安全座椅、衣服玩具等 17 项)、给娃买哪些保险(ins → 各阶段医保和商业险);默认都按路线档次,旧账单数字不变;
· 全选阶段时题序:算哪一段 → 城市 → 头胎二胎 → 医院 → 唐筛/无创 → 顺产剖宫产 → 月子 → 满月百天 → 喂养 → 宝宝用品 → 谁带 → 上学 → 课外班 → 保险 → 月收入,共 15 题;v7 备份 /tmp/huafei-before-v8.html。
⑨ v9(2026-11-04,用户:只算一段时才 8 道、太少,要 12~15 道):
· 新增 21 道分阶段细题(QS 里带 p 的),每题都改对应账单项(G() 档次跟答案走):孕期 营养品/课程运动/妈妈用品/去医院交通,分娩 陪护/脐带血/新生儿基因筛查,
月子 产后修复/催乳/抚触游泳(医院题也问月子,管 42 天复查),0~3 岁 纸尿裤洗护/辅食/自费疫苗,看病去哪(0~18 岁),上学接送,假期安排、出去玩、吃穿、电子产品、零花钱、生日;
· 一次最多 15 题(QMAX):选的阶段多、题超了就先去掉 p 小的细题,去掉的按路线默认;只算任意一段都有 12~14 题,全选仍是 v8 那 15 题;
· 细题默认按路线档次(脐带血只富养默认存,接送跟上学路线),默认作答时账单与 v8 一致;v8 备份 /tmp/huafei-before-v9.html。
存储 localStorage「mmbox.huafei.v5」(兼容读取 v4)。推荐工具走 ../_shared/tools.js;自成一页:不引 app.js;
返回走 history.back 或相对路径 ../../ 并带回 search + hash。v4 备份 /tmp/huafei5/huafei-v4-before-v5.html。 -->
<style>
:root{--c:#18A058;--cd:#0E7A40;--cbg:#EAF7F0;--c4:#F5FBF8;--h:#1F2329;--t:#4E5969;--s:#86909C;--line:#EEF0F3;--gray:#F7F8FA;--warm:#FFF5EA;--warm2:#F4E2CC;--wt:#B4733A;--gold:#C2912E}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%;overflow:hidden;background:#fff;color:var(--t);font:15px/1.75 -apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif}
#scroll{position:fixed;inset:0;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;overscroll-behavior:none;padding-bottom:calc(28px + env(safe-area-inset-bottom))}
img{display:block;max-width:100%}
button{font:inherit;border:0;background:none;color:inherit;cursor:pointer}
.mono{font-family:"SF Mono",Menlo,Consolas,"Roboto Mono","Courier New",monospace;font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.num{font-family:"DIN Alternate","Helvetica Neue",Arial,sans-serif;font-variant-numeric:tabular-nums}
.ic{width:20px;height:20px;flex:none;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.hide{position:absolute;width:0;height:0;overflow:hidden}
.press{transition:transform .15s}
.press:active{transform:scale(.98)}
/* 顶栏 */
.bar{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:4px;height:48px;padding:0 8px;padding-top:env(safe-area-inset-top);background:rgba(255,255,255,0);border-bottom:1px solid transparent;transition:background .2s,border-color .2s}
.bar.on{background:#fff;border-bottom-color:var(--line)}
.bar .back{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;color:var(--h)}
.bar .back .ic{width:22px;height:22px}
.bar .t{font-size:16px;font-weight:600;color:var(--h)}
.bg{position:absolute;left:0;right:0;top:0;height:520px;background:linear-gradient(160deg,var(--cbg) 0%,#fff 70%);overflow:hidden;pointer-events:none;z-index:0}
.bg i{position:absolute;border-radius:50%;background:rgba(24,160,88,.08)}
.bg i:nth-child(1){width:280px;height:280px;right:-100px;top:-90px}
.bg i:nth-child(2){width:130px;height:130px;left:-56px;top:240px;background:rgba(24,160,88,.06)}
.screen{display:none;position:relative;z-index:1;padding:0 20px}
.screen.on{display:block;animation:fd .35s ease both}
@keyframes fd{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.fu{opacity:0;transform:translateY(12px);transition:opacity .45s ease,transform .45s ease}
.fu.in{opacity:1;transform:none}
.btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:48px;border-radius:99px;background:var(--c);color:#fff;font-size:16px;font-weight:700;box-shadow:0 8px 20px rgba(24,160,88,.22);transition:opacity .2s,transform .15s}
.btn:active{transform:scale(.98)}
.btn[disabled]{opacity:.4;box-shadow:none}
.btn.ghost{background:#fff;color:var(--c);border:1px solid var(--c);box-shadow:none}
.btn .ic{width:18px;height:18px}
.chip{display:inline-block;font-size:12px;font-weight:600;line-height:22px;padding:0 10px;border-radius:99px;background:var(--cbg);color:var(--c)}
.mk{background:linear-gradient(transparent 62%,rgba(24,160,88,.2) 0)}
/* ============ 开始屏:路线 ============ */
.hero{position:relative;display:flex;align-items:center;gap:12px;padding-top:6px}
.hero .tx{flex:1;min-width:0;position:relative;z-index:1}
h1{font-size:min(36px,9.6vw);font-weight:900;line-height:1.2;color:var(--h);letter-spacing:.02em}
h1 .mk{background:linear-gradient(transparent 64%,rgba(24,160,88,.24) 0)}
.subt{margin-top:8px;font-size:min(17px,4.5vw);font-weight:700;line-height:1.45;color:var(--t)}
.lede{margin-top:6px;font-size:13px;color:var(--s);line-height:1.6}
.hero .pic{flex:none;width:min(118px,29vw);height:min(118px,29vw);border-radius:28px;overflow:hidden;background:#fff;box-shadow:0 8px 24px rgba(24,160,88,.12)}
.hero .pic img{width:100%;height:100%;object-fit:cover;object-position:36% 50%;transform:scale(1.25)}
.pick-h{display:flex;align-items:baseline;justify-content:space-between;margin-top:22px}
.pick-h h2{font-size:19px;font-weight:800;color:var(--h)}
.pick-h small{font-size:12px;color:var(--s)}
.routes{margin-top:12px;display:flex;flex-direction:column;gap:10px}
.rt{position:relative;display:block;width:100%;padding:14px 14px 13px;border-radius:22px;background:#fff;border:1.5px solid var(--line);text-align:left;overflow:hidden;transition:border-color .2s,box-shadow .2s,background .2s,transform .15s}
.rt:active{transform:scale(.985)}
.rt .top{display:flex;align-items:center;gap:10px}
.rt .no{flex:none;width:42px;height:42px;border-radius:14px;display:grid;place-items:center;background:var(--gray);color:#A3A9B1;font-size:20px;font-weight:800;letter-spacing:-.02em;transition:all .2s}
.rt .bd{flex:none;width:46px;height:46px}
.rt .nm{flex:1;min-width:0}
.rt .nm small{display:block;margin-top:1px;font-size:12.5px;color:var(--s);line-height:1.45}
.rt .nm b{display:block;font-size:min(17px,4.6vw);font-weight:800;color:var(--h);line-height:1.35;white-space:nowrap}
.rt .nm b i{font-style:normal;color:var(--c)}
.rt .ok{position:absolute;right:0;top:0;width:34px;height:30px;border-radius:0 0 0 14px;background:var(--c);color:#fff;display:grid;place-items:center;opacity:0;transform:scale(.6);transform-origin:top right;transition:all .2s}
.rt .ok .ic{width:16px;height:16px;stroke-width:2.8}
.rt .tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:10px}
.rt .tags span{font-size:11.5px;line-height:22px;padding:0 7px;border-radius:8px;background:var(--gray);color:var(--t)}
.rt.on{border-color:var(--c);background:linear-gradient(150deg,#F2FBF6,#fff 70%);box-shadow:0 10px 26px rgba(24,160,88,.16)}
.rt.on .no{background:var(--c);color:#fff;box-shadow:0 4px 10px rgba(24,160,88,.3)}
.rt.on .ok{opacity:1;transform:none}
.rt.on .tags span{background:#fff;color:var(--cd)}
.go{position:sticky;bottom:0;z-index:5;margin:16px -20px 0;padding:12px 20px calc(12px + env(safe-area-inset-bottom));background:linear-gradient(rgba(255,255,255,0),#fff 30%)}
.last{display:none;margin:4px auto 0;padding:4px 10px;font-size:14px;color:var(--c);font-weight:600}
.last.on{display:block}
.tiny{margin-top:8px;font-size:12px;color:var(--s);text-align:center;line-height:1.6}
/* 选阶段 */
.stg{margin-top:18px}
.stg .all{display:flex;align-items:center;gap:12px;width:100%;padding:14px 16px;border-radius:18px;border:1.5px solid var(--line);background:#fff;text-align:left;transition:all .15s}
.stg .all .ic{width:22px;height:22px;color:var(--c)}
.stg .all b{display:block;font-size:16px;font-weight:800;color:var(--h)}
.stg .all small{display:block;font-size:12.5px;color:var(--s)}
.stg .all .ck,.sc .ck{flex:none;margin-left:auto;width:22px;height:22px;border-radius:7px;border:1.5px solid #D5D9DE;display:grid;place-items:center;color:#fff;transition:all .15s}
.stg .ck .ic{width:14px;height:14px;stroke-width:2.8;opacity:0;color:#fff}
.stg .all.on{border-color:var(--c);background:var(--c4)}
.stg .on .ck{background:var(--c);border-color:var(--c)}
.stg .on .ck .ic{opacity:1}
.sgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px}
.sc{position:relative;display:flex;flex-direction:column;align-items:flex-start;min-width:0;padding:10px 10px 9px;border-radius:14px;border:1.5px solid var(--line);background:#fff;text-align:left;transition:all .15s}
.sc b{font-size:15px;font-weight:800;color:var(--h);line-height:1.35;white-space:nowrap}
.sc small{font-size:11px;color:var(--s);line-height:1.4;white-space:nowrap}
.sc .ck{position:absolute;right:7px;top:8px;width:18px;height:18px;border-radius:6px}
.sc .ck .ic{width:12px;height:12px}
.sc.on{border-color:var(--c);background:var(--c4)}
.sc.on b{color:var(--cd)}
.sc .gi{font-style:normal;font-size:10.5px;font-weight:700;color:#B5BAC2;letter-spacing:.06em}
.sc.on .gi{color:var(--c)}
.shint{margin-top:10px;font-size:12px;color:var(--s);line-height:1.6}
.qgo{margin-top:18px}
/* 填收入 */
.inc{margin-top:18px;display:flex;align-items:center;gap:6px;height:64px;padding:0 16px;border-radius:18px;border:1.5px solid var(--line);background:#fff;transition:border-color .15s}
.inc:focus-within{border-color:var(--c);box-shadow:0 0 0 4px rgba(24,160,88,.1)}
.inc span{font-size:20px;font-weight:800;color:var(--h)}
.inc input{flex:1;min-width:0;height:100%;border:0;outline:0;background:none;font:800 26px/1 "DIN Alternate","Helvetica Neue",Arial,sans-serif;color:var(--h);-webkit-appearance:none;appearance:none}
.inc input::placeholder{font-size:16px;font-weight:500;color:#C0C5CC;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif}
.inc input::-webkit-outer-spin-button,.inc input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.inc em{flex:none;font-style:normal;font-size:14px;color:var(--s)}
.qk{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.qk button{height:34px;padding:0 14px;border-radius:99px;border:1px solid #CFEBDB;background:#fff;font-size:14px;font-weight:600;color:var(--cd)}
.qk button.on{background:var(--c);border-color:var(--c);color:#fff}
.priv{display:flex;align-items:center;gap:6px;margin-top:12px;font-size:12.5px;color:var(--s)}
.priv .ic{width:15px;height:15px;color:var(--c)}
.skip2{display:block;margin:12px auto 0;padding:6px 10px;font-size:14px;color:var(--s)}
/* 账单里的收入对比 */
.rc-inc{margin-top:12px;padding:12px 12px 10px;border-radius:10px;background:#F6FAF8;border:1.5px dashed #BFE3CE}
.rc-inc .h{font-size:12px;color:var(--s)}
.rc-inc .h b{color:var(--h)}
.rc-inc .big{margin-top:2px;font-size:15px;font-weight:800;color:var(--h)}
.rc-inc .big em{font-style:normal;color:var(--c);font-size:20px}
.pbar{position:relative;height:10px;margin-top:10px;border-radius:9px;background:#E3EEE8;overflow:hidden}
.pbar i{position:absolute;left:0;top:0;bottom:0;border-radius:9px;background:linear-gradient(90deg,#79CC9E,var(--c))}
.pbar i.hot{background:linear-gradient(90deg,#F2B57A,#D9823B)}
.pl{display:flex;justify-content:space-between;gap:8px;margin-top:5px;font-size:12px;color:var(--t)}
.pl b{color:var(--h)}
.pl .w{color:var(--wt);font-weight:700}
.rc-tip{margin-top:6px;font-size:11.5px;color:var(--s)}
/* ============ 问题屏 ============ */
.qtop{display:flex;align-items:center;gap:10px;margin-top:4px}
.qtop .rp{flex:none;display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px 0 4px;border-radius:99px;background:var(--cbg);font-size:12.5px;font-weight:700;color:var(--cd)}
.qtop .rp svg{width:22px;height:22px}
.qtop .dots{flex:1;display:flex;gap:5px;justify-content:flex-end}
.qtop .dots i{width:18px;height:5px;border-radius:9px;background:var(--line);transition:background .3s,width .3s}
.qtop .dots i.d{background:#9ED8B7}
.qtop .dots i.c{width:28px;background:var(--c)}
.qn{margin-top:26px;font-size:13px;color:var(--s)}
.qn b{color:var(--c);font-weight:800}
.qh{margin-top:4px;font-size:24px;font-weight:800;line-height:1.35;color:var(--h)}
.qs{margin-top:4px;font-size:13.5px;color:var(--s)}
.opts{margin-top:20px;display:flex;flex-direction:column;gap:10px}
.opts.hide2{display:none}
.opt{display:flex;align-items:center;gap:12px;width:100%;min-height:70px;padding:12px 14px;border-radius:18px;border:1.5px solid var(--line);background:#fff;text-align:left;transition:border-color .15s,background .15s,transform .15s}
.opt:active{transform:scale(.98)}
.opt .oi{flex:none;width:42px;height:42px;border-radius:13px;background:var(--c4);color:var(--c);display:grid;place-items:center;transition:background .15s,color .15s}
.opt .oi .ic{width:22px;height:22px}
.opt .ox{flex:1;min-width:0}
.opt b{display:block;font-size:16px;font-weight:700;color:var(--h);line-height:1.4}
.opt b em{display:inline-block;vertical-align:2px;margin-left:6px;font-style:normal;font-size:10.5px;font-weight:700;line-height:17px;padding:0 6px;border-radius:99px;background:var(--cbg);color:var(--c)}
.opt small{display:block;margin-top:1px;font-size:12.5px;color:var(--s);line-height:1.45}
.opt .pp{display:block;margin-top:3px;font-size:12.5px;font-weight:700;color:var(--c);line-height:1.4;font-variant-numeric:tabular-nums}
.opt .rd{flex:none;width:22px;height:22px;border-radius:50%;border:1.5px solid #D5D9DE;display:grid;place-items:center}
.opt.on{border-color:var(--c);background:var(--c4)}
.opt.on .oi{background:var(--c);color:#fff}
.opt.on .rd{border-color:var(--c);background:var(--c)}
.opt.on .rd::after{content:"";width:8px;height:8px;border-radius:50%;background:#fff}
.qa{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:20px}
.prev{display:inline-flex;align-items:center;gap:2px;padding:8px 4px;font-size:14px;color:var(--s)}
.prev .ic{width:18px;height:18px}
.keep{display:none;align-items:center;gap:2px;height:36px;padding:0 10px 0 14px;border-radius:99px;border:1px solid var(--line);font-size:13.5px;color:var(--t);background:#fff;white-space:nowrap}
.keep.on{display:inline-flex}
.keep .ic{width:16px;height:16px}
.qtip{margin-top:14px;font-size:12px;color:#B5BAC2;text-align:center}
/* ============ 结果屏:账单 ============ */
.r-top{display:flex;align-items:center;gap:8px;margin-top:4px}
.r-top .md{font-size:12.5px;color:var(--s)}
.r-t{margin-top:8px;font-size:24px;font-weight:800;line-height:1.35;color:var(--h)}
.r-t em{font-style:normal;color:var(--c)}
.r-s{margin-top:4px;font-size:13px;color:var(--s)}
.printer{position:relative;z-index:3;margin-top:18px;height:26px;border-radius:14px;background:linear-gradient(#2A3B33,#1C2A23);box-shadow:0 6px 14px rgba(20,40,30,.18)}
.printer::before{content:"";position:absolute;left:12px;right:12px;bottom:6px;height:5px;border-radius:4px;background:#0B1510}
.printer .led{position:absolute;right:14px;top:6px;width:6px;height:6px;border-radius:50%;background:#45D483;box-shadow:0 0 6px #45D483}
.printer .lb{position:absolute;left:14px;top:2px;font-size:9px;letter-spacing:.2em;color:rgba(255,255,255,.4);font-weight:700}
.printer.busy .led{animation:blink .5s steps(2) infinite}
@keyframes blink{50%{opacity:.2}}
.feed{position:relative;z-index:2;margin:-9px 10px 0;padding:0 4px 18px;overflow:hidden}
.paper{position:relative;padding:22px 16px 26px;background:#fff;color:#2B2F36;filter:drop-shadow(0 6px 10px rgba(30,50,40,.12))}
.paper::after{content:"";position:absolute;left:0;right:0;bottom:-8px;height:8px;background:conic-gradient(from -45deg at 50% 100%,#fff 90deg,transparent 0) 0 0/12px 8px repeat-x}
.paper::before{content:"";position:absolute;inset:0;background:repeating-linear-gradient(transparent 0 3px,rgba(0,0,0,.012) 3px 4px);pointer-events:none}
.rc-h{text-align:center}
.rc-logo{display:inline-flex;align-items:center;gap:6px;font-size:17px;font-weight:800;color:var(--h);letter-spacing:.06em}
.rc-logo svg{width:22px;height:22px;color:var(--c)}
.rc-sub{margin-top:2px;font-size:11.5px;color:var(--s);letter-spacing:.3em}
.rc-meta{margin-top:12px;font-size:12px;line-height:1.75;color:var(--t)}
.rc-meta div{display:flex;justify-content:space-between;gap:10px}
.rc-meta span:last-child{text-align:right;min-width:0}
.hr{height:0;margin:12px 0;border-top:1.5px dashed #C9CED4}
.hr.s{border-top-style:solid;border-top-width:1px;border-color:#2B2F36}
.hr.dbl{border-top:3px double #2B2F36}
.rc-sec{margin-top:4px}
.rc-st{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}
.rc-st b{font-size:14px;font-weight:800;color:var(--h)}
.rc-st b i{display:inline-block;min-width:22px;margin-right:4px;font-style:normal;font-size:11px;font-weight:700;color:#fff;background:var(--c);border-radius:5px;text-align:center;line-height:17px;vertical-align:1px;padding:0 4px}
.rc-st span{font-size:11.5px;color:var(--s)}
.li{display:flex;align-items:baseline;gap:6px;font-size:13px;line-height:1.95;color:#3B4048}
.li .n{flex:none;max-width:68%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.li .d{flex:1;min-width:8px;border-bottom:1.5px dotted #D3D7DC;transform:translateY(-4px)}
.li .v{flex:none;font-size:13px}
.li.neg .v{color:var(--c)}
.li.neg .n{color:var(--c)}
.sub{display:flex;justify-content:space-between;align-items:baseline;margin-top:4px;padding-top:4px;border-top:1px solid #EEF0F3;font-size:13px;font-weight:800;color:var(--h)}
.sub .v{font-size:14px}
.rc-tot{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.rc-tot span{font-size:15px;font-weight:800;color:var(--h);letter-spacing:.3em}
.rc-tot b{font-size:clamp(26px,8.2vw,32px);font-weight:800;color:var(--h);white-space:nowrap}
.rc-rg{margin-top:2px;text-align:right;font-size:12.5px;color:var(--t)}
.rc-rg em{font-style:normal;font-weight:800;color:var(--c)}
.rc-nodes{display:grid;grid-template-columns:repeat(3,1fr);margin-top:12px;border:1.5px dashed #C9CED4;border-radius:10px}
.rc-nodes div{padding:8px 4px;text-align:center}
.rc-nodes div+div{border-left:1.5px dashed #C9CED4}
.rc-nodes span{display:block;font-size:11px;color:var(--s);line-height:1.5}
.rc-nodes b{display:block;font-size:15px;font-weight:800;color:var(--h);line-height:1.4}
.rc-x{margin-top:10px;font-size:12px;line-height:1.85;color:var(--t)}
.rc-x .li{font-size:12px;color:var(--t)}
.rc-x .li .v{font-size:12px}
.rc-x .warm .n,.rc-x .warm .v{color:var(--wt);font-weight:700}
.rc-f{margin-top:14px;text-align:center;font-size:11.5px;color:var(--s);line-height:1.7}
.rc-f b{display:block;font-size:13px;color:var(--h);letter-spacing:.2em}
.bc{display:block;width:78%;height:40px;margin:10px auto 2px}
.rc-no{font-size:10.5px;letter-spacing:.3em;color:var(--s)}
.savebill{margin-top:6px}
.racts{display:flex;gap:10px;margin-top:10px}
.racts .btn{flex:1;height:44px;font-size:15px}
/* ============ 收尾一行小提示 ============ */
.hint{margin-top:22px;font-size:12.5px;line-height:1.6;color:#B4936F;text-align:center}
.blk{margin-top:30px}
.note{margin-top:16px;font-size:12px;color:var(--s);line-height:1.65;text-align:center}
.note b{color:var(--t)}
.toast{position:fixed;left:50%;bottom:calc(80px + env(safe-area-inset-bottom));transform:translate(-50%,8px);max-width:300px;padding:10px 16px;border-radius:12px;background:rgba(31,35,41,.88);color:#fff;font-size:13.5px;line-height:1.6;text-align:center;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;z-index:60}
.toast.on{opacity:1;transform:translate(-50%,0)}
.mask{position:fixed;inset:0;z-index:50;display:none;flex-direction:column;align-items:center;justify-content:center;padding:24px 28px calc(24px + env(safe-area-inset-bottom));background:rgba(18,28,22,.82)}
.mask.on{display:flex;animation:fd .25s ease both}
.mwrap{max-width:100%;max-height:calc(100% - 96px);display:flex;border-radius:16px;box-shadow:0 12px 32px rgba(0,0,0,.25)}
.mask img{max-width:100%;max-height:100%;border-radius:16px;-webkit-touch-callout:default;user-select:auto}
.mask.long .mwrap{display:block;width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;background:#EAF7F0}
.mask.long img{width:100%;max-height:none;border-radius:0}
.mask p{margin-top:14px;color:#fff;font-size:14.5px;font-weight:600;display:flex;align-items:center;gap:6px}
.mask .row{display:flex;gap:16px;margin-top:10px}
.mask .row button,.mask .row a{height:34px;padding:0 18px;border-radius:99px;border:1px solid rgba(255,255,255,.6);color:#fff;font-size:13.5px;line-height:32px;text-decoration:none}
@media (prefers-reduced-motion:reduce){.screen.on,.mask.on{animation:none}.fu{opacity:1;transform:none;transition:none}}
</style>
<script src="../../js/host-nav.js?v=26110908"></script>
</head>
<body>
<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-right" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M20 11a8 8 0 0 0-14.3-4.9L4 8M4 4v4h4M4 13a8 8 0 0 0 14.3 4.9L20 16M20 20v-4h-4"/></symbol>
<symbol id="i-edit" viewBox="0 0 24 24"><path d="M4 20h4L19 9l-4-4L4 16z"/><path d="M13.5 6.5l4 4"/></symbol>
<symbol id="i-img" viewBox="0 0 24 24"><rect x="3.5" y="4" width="17" height="16" rx="3"/><circle cx="9" cy="9.5" r="1.8"/><path d="M4 17l4.5-4.5 3.5 3.5 2.5-2.5L20 18.5"/></symbol>
<symbol id="i-hand" viewBox="0 0 24 24"><path d="M9 11V5.5a1.5 1.5 0 0 1 3 0V11M12 10V4.5a1.5 1.5 0 0 1 3 0V11M15 10.5V6a1.5 1.5 0 0 1 3 0v8c0 4-2.5 6.5-6 6.5-2.6 0-4.2-1.3-5.6-3.6L4.3 13.6a1.5 1.5 0 0 1 2.5-1.6L9 14.5V8.5"/></symbol>
<symbol id="i-leaf" viewBox="0 0 24 24"><path d="M5 19c0-8 5-14 15-15-1 10-7 15-15 15z"/><path d="M5 19l8-8"/></symbol>
<symbol id="o-shield2" viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 5-3 8-7 10-4-2-7-5-7-10V6z"/><path d="M9 12l2 2 4-4"/></symbol>
<!-- 选项图标 -->
<symbol id="o-hosp" viewBox="0 0 24 24"><path d="M3 21h18M5 21V8h14v13M9 21v-4h6v4"/><path d="M12 10v5M9.5 12.5h5"/><path d="M9 8V4.5h6V8"/></symbol>
<symbol id="o-star" viewBox="0 0 24 24"><path d="M3 21h12M5 21V9h8v12M8 12.5h2M8 16h2"/><path d="M18 3.5l1.2 2.5 2.6.4-1.9 1.8.5 2.6-2.4-1.3-2.4 1.3.5-2.6-1.9-1.8 2.6-.4z"/></symbol>
<symbol id="o-gem" viewBox="0 0 24 24"><path d="M6.5 4h11L21 9l-9 11L3 9z"/><path d="M3 9h18M9.5 4L8 9l4 11 4-11-1.5-5"/></symbol>
<symbol id="o-heart" viewBox="0 0 24 24"><path d="M12 20c-5-3.6-8.5-7-8.5-11 0-2.7 2-4.7 4.5-4.7 1.7 0 3.1.9 4 2.4.9-1.5 2.3-2.4 4-2.4 2.5 0 4.5 2 4.5 4.7 0 4-3.5 7.4-8.5 11z"/></symbol>
<symbol id="o-med" viewBox="0 0 24 24"><rect x="3.5" y="6.5" width="17" height="13" rx="3"/><path d="M9 6.5V4.5h6v2M12 10v6M9 13h6"/></symbol>
<symbol id="o-ask" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9.5 9.5a2.5 2.5 0 1 1 3.6 2.2c-.7.4-1.1.9-1.1 1.8v.5"/><path d="M12 17v.2"/></symbol>
<symbol id="o-drop" viewBox="0 0 24 24"><path d="M12 3.5c3.5 4.3 6 7.6 6 10.8a6 6 0 0 1-12 0c0-3.2 2.5-6.5 6-10.8z"/><path d="M9.2 14.5a2.9 2.9 0 0 0 2.6 2.8"/></symbol>
<symbol id="o-mix" viewBox="0 0 24 24"><path d="M7 3.5c2.3 2.8 4 5 4 7.2a4 4 0 0 1-8 0c0-2.2 1.7-4.4 4-7.2z"/><path d="M15 8h4M15.5 8v3l-1 1.5V20h5.5v-7.5l-1-1.5V8"/></symbol>
<symbol id="o-bottle" viewBox="0 0 24 24"><path d="M10 2.5h4M10.5 2.5v2.5L9 6.5h6l-1.5-1.5V2.5"/><path d="M8 9l1-2.5h6L16 9v10.5a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2z"/><path d="M8 12.5h3M8 15.5h3M8 18.5h3"/></symbol>
<symbol id="o-bldg" viewBox="0 0 24 24"><path d="M3 21h18M5 21V9l5-3v15M10 21V3l9 4v14M13.5 9h2M13.5 12.5h2M13.5 16h2M7 12h1M7 15.5h1"/></symbol>
<symbol id="o-dna" viewBox="0 0 24 24"><path d="M8 3c0 6 8 6 8 9s-8 3-8 9M16 3c0 6-8 6-8 9s8 3 8 9M9.5 6.5h5M9.5 17.5h5"/></symbol>
<symbol id="o-cake" viewBox="0 0 24 24"><path d="M4 21h16M5 21v-7h14v7M5 17.5c2.3 1 4.7 1 7 0s4.7-1 7 0M8 14v-3M12 14v-3M16 14v-3M8 7.5v.01M12 7.5v.01M16 7.5v.01"/></symbol>
<symbol id="o-camera" viewBox="0 0 24 24"><path d="M4 8h3l2-3h6l2 3h3v11H4zM12 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></symbol>
<symbol id="o-recycle" viewBox="0 0 24 24"><path d="M7 19H4.5l2.6-4.5M7.1 14.5 5.5 17M10 5.5l2-3.5 2.6 4.5M14.6 6.5 12 6M19.5 14.5 17 19h-5M12 19l2 2M5.5 11 4 8.5M18 9.5l1.5 2.5"/></symbol>
<symbol id="o-cart" viewBox="0 0 24 24"><path d="M3 4h2.5l2 11h10.5l2-8H6.3M9 20h.01M17 20h.01"/></symbol>
<symbol id="o-umb" viewBox="0 0 24 24"><path d="M3 12a9 9 0 0 1 18 0zM12 12v6.5a2 2 0 0 0 4 0M12 3v-.5"/></symbol>
<symbol id="o-bus" viewBox="0 0 24 24"><path d="M6 17V6a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v11zM6 11h12M6 17v2.5M18 17v2.5M9 14.5h.01M15 14.5h.01"/></symbol>
<symbol id="o-car" viewBox="0 0 24 24"><path d="M4 16v-3l2-5h12l2 5v3zM4 16v2.5M20 16v2.5M4 13h16M8 15h.01M16 15h.01"/></symbol>
<symbol id="o-plane" viewBox="0 0 24 24"><path d="M3 13.5 21 6l-4.5 13-4-5.5zM12.5 13.5 21 6M12.5 13.5V19l2-3"/></symbol>
<symbol id="o-syringe" viewBox="0 0 24 24"><path d="m17 3 4 4M19 5l-3 3M16 8l-9 9-3 1 1-3 9-9zM10 8l6 6M8 13l2 2"/></symbol>
<symbol id="o-bowl" viewBox="0 0 24 24"><path d="M3 11h18a9 9 0 0 1-18 0zM8 20h8M9 7c0-1.5 1-1.5 1-3M13 7c0-1.5 1-1.5 1-3"/></symbol>
<symbol id="o-coin" viewBox="0 0 24 24"><path d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18zM9 8l3 4 3-4M12 12v5M9.5 12.5h5M9.5 15h5"/></symbol>
<symbol id="o-bldg2" viewBox="0 0 24 24"><path d="M3 21h18M5 21V10h6v11M11 21V6h8v15M14 9.5h2M14 13h2M14 16.5h2M7.5 13.5h1M7.5 17h1"/></symbol>
<symbol id="o-house" viewBox="0 0 24 24"><path d="M3.5 11L12 4l8.5 7M6 9.5V20h12V9.5"/><path d="M10 20v-5h4v5"/></symbol>
<symbol id="o-tree" viewBox="0 0 24 24"><path d="M2.5 13L8 8.5l5.5 4.5M4.5 11.5V20h7v-8.5"/><path d="M17.5 20v-5M17.5 4.5c-2.2 0-3.5 2.5-3.5 5 0 2.8 1.6 4.5 3.5 4.5s3.5-1.7 3.5-4.5c0-2.5-1.3-5-3.5-5zM2 20h20"/></symbol>
<symbol id="o-one" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M10 9l2.5-1.5V16.5M10 16.5h5"/></symbol>
<symbol id="o-layers" viewBox="0 0 24 24"><path d="M12 3.5l9 4.5-9 4.5L3 8z"/><path d="M3 12l9 4.5 9-4.5M3 16l9 4.5 9-4.5"/></symbol>
<symbol id="o-family" viewBox="0 0 24 24"><circle cx="8" cy="7.5" r="2.8"/><circle cx="16.5" cy="10" r="2.2"/><path d="M2.5 20c.5-3.6 2.7-5.8 5.5-5.8s5 2.2 5.5 5.8M13.5 20c.3-2.4 1.5-4 3-4s2.7 1.6 3 4"/></symbol>
<symbol id="o-flag" viewBox="0 0 24 24"><path d="M3.5 20.5h17M5.5 20.5V11L12 6.5l6.5 4.5v9.5M12 6.5V2.5l3.5 1.5L12 5.5"/><path d="M10 20.5v-4.5h4v4.5"/></symbol>
<symbol id="o-nurse" viewBox="0 0 24 24"><circle cx="12" cy="8" r="3.5"/><path d="M5 20.5c.8-3.8 3.6-6 7-6s6.2 2.2 7 6"/><path d="M12 15.5v3.5M10.25 17.25h3.5"/></symbol>
<symbol id="o-school" viewBox="0 0 24 24"><path d="M3 20.5h18M4.5 20.5V9.5h15v11M3 9.5L12 4l9 5.5"/><circle cx="12" cy="13" r="2"/><path d="M8 20.5v-3M16 20.5v-3"/></symbol>
<symbol id="o-book" viewBox="0 0 24 24"><path d="M3 5.5c3-1 6-.8 9 1v13c-3-1.8-6-2-9-1zM21 5.5c-3-1-6-.8-9 1v13c3-1.8 6-2 9-1z"/></symbol>
<symbol id="o-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.6 3.8 5.6 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3z"/></symbol>
<symbol id="o-cap" viewBox="0 0 24 24"><path d="M2.5 9L12 4.5 21.5 9 12 13.5z"/><path d="M6.5 11v4.5c1.5 1.5 3.3 2.2 5.5 2.2s4-.7 5.5-2.2V11M21.5 9v5"/></symbol>
<symbol id="o-sprout" viewBox="0 0 24 24"><path d="M12 21v-9"/><path d="M12 12c0-4-3-6.5-7-6.5 0 4 3 6.5 7 6.5zM12 14c0-3.5 2.6-6 6.5-6 0 3.5-2.6 6-6.5 6z"/></symbol>
<symbol id="o-music" viewBox="0 0 24 24"><path d="M9 18V5.5l11-2V16"/><circle cx="6.5" cy="18" r="2.5"/><circle cx="17.5" cy="16" r="2.5"/></symbol>
<symbol id="o-rocket" viewBox="0 0 24 24"><path d="M12 3c3 2 4.5 5.5 4.5 9.5L14 16h-4l-2.5-3.5C7.5 8.5 9 5 12 3z"/><circle cx="12" cy="9.5" r="1.6"/><path d="M7.5 12.5L5 15l2.5 1.5M16.5 12.5L19 15l-2.5 1.5M10.5 19l1.5 2 1.5-2"/></symbol>
<!-- 路线徽章(带颜色,不吃 .ic) -->
<symbol id="b-rich" viewBox="0 0 56 56"><circle cx="28" cy="28" r="27" fill="#FFF5DC"/><circle cx="28" cy="28" r="22" fill="none" stroke="#E8C66E" stroke-width="1.4" stroke-dasharray="2 3"/><path d="M15 34l-2-14 8 6 7-10 7 10 8-6-2 14z" fill="#F2C94C" stroke="#B88A1E" stroke-width="1.6" stroke-linejoin="round"/><rect x="15" y="35.5" width="26" height="4.5" rx="1.6" fill="#E3B23C" stroke="#B88A1E" stroke-width="1.4"/><circle cx="28" cy="27.5" r="2.4" fill="#18A058"/><circle cx="20" cy="29" r="1.6" fill="#fff"/><circle cx="36" cy="29" r="1.6" fill="#fff"/></symbol>
<symbol id="b-mid" viewBox="0 0 56 56"><circle cx="28" cy="28" r="27" fill="#E3F5EA"/><path d="M14 27l14-11 14 11" fill="none" stroke="#0E7A40" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17.5 25v15h21V25" fill="#fff" stroke="#0E7A40" stroke-width="1.8" stroke-linejoin="round"/><rect x="25" y="31" width="6" height="9" rx="1" fill="#18A058"/><rect x="19.8" y="28.5" width="4" height="4" rx=".8" fill="#9ED8B7"/><rect x="32.2" y="28.5" width="4" height="4" rx=".8" fill="#9ED8B7"/><path d="M44 40V31" stroke="#0E7A40" stroke-width="1.6" stroke-linecap="round"/><ellipse cx="44" cy="29" rx="3.6" ry="5" fill="#45B779"/><path d="M11 40h35" stroke="#0E7A40" stroke-width="1.6" stroke-linecap="round"/></symbol>
<symbol id="b-low" viewBox="0 0 56 56"><circle cx="28" cy="28" r="27" fill="#EEF7F2"/><path d="M13.5 30c0-6.6 6.3-11 14-11 3 0 5.7.6 8 1.8l4-2.6-.6 5c1.6 1.6 2.6 3.6 2.9 5.8h2.2v5h-2.8c-1 2.4-3 4.4-5.5 5.6V43h-4.5v-2.6c-1.2.2-2.4.3-3.7.3s-2.4-.1-3.6-.3V43h-4.5v-3.7c-3.5-2.1-5.9-5.4-5.9-9.3z" fill="#BDE6CD" stroke="#0E7A40" stroke-width="1.6" stroke-linejoin="round"/><circle cx="37" cy="27.5" r="1.4" fill="#0E7A40"/><path d="M24 20.3h7" stroke="#0E7A40" stroke-width="2" stroke-linecap="round"/><circle cx="27.5" cy="11.5" r="4.2" fill="#F2C94C" stroke="#B88A1E" stroke-width="1.3"/><path d="M27.5 9.6v3.8" stroke="#B88A1E" stroke-width="1.2" stroke-linecap="round"/></symbol>
</svg>
<div id="scroll">
<div class="bg" id="bgTop"><i></i><i></i></div>
<header class="bar" id="bar">
<button class="back" id="back" aria-label="返回"><svg class="ic"><use href="#i-back" xlink:href="#i-back"/></svg></button>
<span class="t">孕期专题</span>
</header>
<!-- 开始屏:选路线 -->
<section class="screen" id="sStart">
<div class="hero">
<div class="tx">
<h1><span class="mk">生娃计算器</span></h1>
<p class="subt">养一个孩子<br>到底要花多少钱?</p>
</div>
<div class="pic"><img src="img/hero.webp" alt="孕妈拿着计算器记账"></div>
</div>
<p class="lede">选路线、答几题,给你打一张养娃账单。</p>
<div class="pick-h"><h2>先选你家的养娃路线</h2><small>三选一</small></div>
<div class="routes" id="routes"></div>
<div class="go"><button class="btn" id="goQ" disabled>先选一条路线</button></div>
<button class="last" id="goLast">看上次的账单</button>
<p class="tiny">不用填个人信息 · 选择只存在你的手机上</p>
</section>
<!-- 问题屏 -->
<section class="screen" id="sQ">
<div class="qtop"><span class="rp" id="rp"></span><span class="dots" id="dots"></span></div>
<div class="qn" id="qn"></div>
<h2 class="qh" id="qh"></h2>
<p class="qs" id="qs"></p>
<div class="opts" id="opts"></div>
<div id="qx"></div>
<div class="qa">
<button class="prev" id="prev"><svg class="ic"><use href="#i-back" xlink:href="#i-back"/></svg><span id="prevT">上一题</span></button>
<button class="keep press" id="keep"><span id="keepT">就这样,下一题</span><svg class="ic"><use href="#i-right" xlink:href="#i-right"/></svg></button>
</div>
<p class="qtip" id="qtip">选完自动下一题</p>
</section>
<!-- 结果屏 -->
<section class="screen" id="sRes">
<div class="r-top"><span class="chip" id="rRoute"></span><span class="md" id="rMeta"></span></div>
<h2 class="r-t" id="rTitle"></h2>
<p class="r-s" id="rSub"></p>
<div class="printer" id="printer"><span class="lb">BILL</span><span class="led"></span></div>
<div class="feed" id="feed"><div class="paper" id="paper"></div></div>
<button class="btn press savebill" id="billBtn"><svg class="ic"><use href="#i-img" xlink:href="#i-img"/></svg>保存账单图片</button>
<!-- 11-02 用户:「换条路线再算」「改一下答案」都去掉,保存账单图片下面只留一个「重新测试」(清空路线和答案,回到开始屏) -->
<div class="racts">
<button class="btn ghost press" id="reTest"><svg class="ic"><use href="#i-refresh" xlink:href="#i-refresh"/></svg>重新测试</button>
</div>
<p class="hint">账单算完了,也记得和家人说说心里话。</p>
<div id="tools" class="fu"></div>
<p class="note"><b>估算区间,仅供参考。</b>按 2025~2026 年国内常见公开行情粗算,单项取中间值;收入只用来做对比,只存在你的手机上;按有生育保险估报销、剖宫产按无并发症估,不含早产、大病、住院等意外支出,不计通胀和房子;各地各家差别很大,看个大概就好。</p>
</section>
</div>
<div class="mask" id="mask">
<div class="mwrap" id="mwrap"><img id="maskImg" alt="我的养娃账单"></div>
<p><svg class="ic"><use href="#i-hand" xlink:href="#i-hand"/></svg><span id="maskTip">长按图片,保存到相册</span></p>
<div class="row"><a id="dlBtn" download="养娃账单.png" hidden>下载图片</a><button id="maskClose">关闭</button></div>
</div>
<div class="toast" id="toast"></div>
<script src="../_shared/tools.js"></script>
<script>
(function () {
'use strict';
/* ================= 路线 ================= */
var ROUTES = [
{ id: 'low', badge: 'b-low', who: '我是普通人', how: '量力养', line: '能省则省,爱一点不少',
tags: ['公立医院', '家人坐月子', '老人帮带', '能省则省'], def: { hosp: 0, yuezi: 0, care: 0, school: 0, extra: 0, screen: 0, gear: 0, party: 0, ins: 0 } },
{ id: 'mid', badge: 'b-mid', who: '我是中产家', how: '从容养', line: '不卷也不亏待,日子过得舒展',
tags: ['公立特需', '月嫂', '托班', '适度早教'], def: { hosp: 1, yuezi: 1, care: 1, school: 0, extra: 1, screen: 1, gear: 1, party: 1, ins: 1 } },
{ id: 'rich', badge: 'b-rich', who: '我是大富豪', how: '富养娃', line: '钱能解决的,都不让娃将就',
tags: ['私立高端产检', '月子中心', '育儿嫂', '国际学校'], def: { hosp: 2, yuezi: 2, care: 2, school: 2, extra: 2, screen: 2, gear: 2, party: 2, ins: 2 } }
];
function tierOf(r) { return r; } /* 11-02 用户:路线顺序改成 普通人 → 中产 → 大富豪,下标即档次 */ // 档次 T:0 量力 / 1 从容 / 2 富养
/* ================= 阶段 ================= */
var STAGES = [
// [账单名, 账单副标题, 芯片名, 芯片小字, 月数(算平均每月)]
['孕期产检', '怀孕约 10 个月', '孕期', '怀孕 10 个月', 10],
['分娩住院', '生娃那几天', '分娩', '住院几天', 0.2],
['坐月子', '产后 1 个月', '月子', '产后 1 个月', 1],
['0~1 岁', '第 1 年', '0~1 岁', '第 1 年', 12],
['1~3 岁', '第 2、3 年', '1~3 岁', '上幼儿园前', 24],
['幼儿园', '3~6 岁 · 3 年', '幼儿园', '3~6 岁', 36],
['小学', '6~12 岁 · 6 年', '小学', '6~12 岁', 72],
['初中', '12~15 岁 · 3 年', '初中', '12~15 岁', 36],
['高中', '15~18 岁 · 3 年', '高中', '15~18 岁', 36]
];
var ALL = [0, 1, 2, 3, 4, 5, 6, 7, 8];
function has(st, list) { return list.some(function (i) { return st.indexOf(i) >= 0; }); }
/* ================= 问题(按所选阶段出现) ================= */
var QS = [
{ k: 'st', type: 'stage', h: '这次想算哪一段?', s: '默认从怀孕算到 18 岁,也可以只算其中几段' },
{ k: 'city', h: '你家在哪类城市?', s: '月嫂、托班、学费都跟着城市走', o: [
['一线城市', '北京、上海、广州、深圳', 'bldg'], ['新一线及省会', '成都、杭州、武汉、各省会', 'bldg2'],
['普通地级市', '三四线城市', 'house'], ['县城及乡镇', '县城、镇上', 'tree']] },
{ k: 'kid', h: '这是第几个宝宝?', s: '老大的东西能接着用', if: function (st) { return has(st, [0, 1, 2, 3, 4]); }, o: [
['头胎', '推车、衣服、玩具都要新添置', 'one'], ['二胎及以上', '大件和衣物能复用一部分', 'layers']] },
{ k: 'hosp', h: '打算在哪类医院产检、生娃?', s: '产检、分娩和产后复查,主要看医院', if: function (st) { return has(st, [0, 1, 2]); }, o: [
['公立医院普通门诊', '建档产检、普通病房,医保能报', 'hosp'], ['公立医院特需', '特需门诊、单间病房,自费多一些', 'star'],
['私立妇产医院', '产检分娩套餐、VIP 病房', 'gem']] },
{ k: 'screen', h: '唐筛还是无创 DNA?', s: '查胎儿染色体的那一项,价格差好几倍', if: function (st) { return has(st, [0]); }, o: [
['唐氏筛查', '常规抽血筛查,几百元', 'heart'], ['无创 DNA(NIPT)', '准确率更高,一两千元', 'dna'],
['无创 DNA 加强版', '多查几种染色体异常', 'gem']] },
{ k: 'pnut', h: '孕期营养品怎么补?', s: '叶酸、DHA、钙铁、孕妇奶粉', p: 5, if: function (st) { return has(st, [0]); }, o: [
['只补必需的', '叶酸、钙铁按医生说的吃', 'sprout'],
['常规几样都补', 'DHA、钙铁维生素,喝点孕妇奶粉', 'drop'],
['进口全套', '进口营养品,孕妇奶粉天天喝', 'gem']] },
{ k: 'pclass', h: '孕期要上课、做运动吗?', s: '孕妇学校、孕期瑜伽、产前私教', p: 3, if: function (st) { return has(st, [0]); }, o: [
['医院孕妇学校', '免费为主,在家自己练', 'book'],
['报个瑜伽或课程', '孕期瑜伽、分娩课', 'music'],
['请私教', '一对一孕期私教和产前课', 'star']] },
{ k: 'momgear', h: '妈妈自己的用品呢?', s: '孕妇装、孕妇枕、哺乳内衣、吸奶器这些', p: 4, if: function (st) { return has(st, [0, 2]); }, o: [
['够用就行', '宽松旧衣先穿着,少买几件', 'recycle'],
['舒服实用', '买几套孕妇装,吸奶器选好用的', 'cart'],
['买好的', '大牌孕妇装、护肤、电动吸奶器', 'gem']] },
{ k: 'ptrans', h: '去医院产检、生娃怎么去?', s: '十来次产检加上住院,路费也不少', p: 2, if: function (st) { return has(st, [0, 1]); }, o: [
['公交地铁', '能坐公交就不打车', 'bus'],
['打车为主', '孕晚期基本都打车', 'car'],
['专车或自驾', '停车费、专车都算上', 'gem']] },
{ k: 'birth', h: '预计顺产还是剖宫产?', s: '剖宫产手术费更高、要多住几天', d: 2, if: function (st) { return has(st, [1]); }, o: [
['顺产', '一般住院 2~3 天,可选无痛', 'heart'], ['剖宫产', '一般住院 4~6 天,手术和麻醉另算', 'med'],
['还不确定', '按顺产估,再留一点剖宫产的差价', 'ask']] },
{ k: 'stay', h: '住院谁来陪护?', s: '陪护床、护工按天算', p: 3, if: function (st) { return has(st, [1]); }, o: [
['家里人陪', '租个陪护床就行', 'family'],
['请个护工', '白天晚上有人搭把手', 'nurse'],
['一对一护工', '专人照顾妈妈和宝宝', 'star']] },
{ k: 'cord', h: '脐带血要存吗?', s: '自体库一次交费,之后每年还有保管费', p: 4, if: function (st) { return has(st, [1]); }, o: [
['不存', '大多数家庭的选择', 'heart'],
['存自体库', '给宝宝留一份,2 万元左右', 'dna']] },
{ k: 'nbs', h: '新生儿筛查要加项吗?', s: '足跟血、听力是常规,基因筛查要另交钱', p: 1, d: 0, if: function (st) { return has(st, [1]); }, o: [
['只做常规', '足跟血和听力筛查', 'heart'],
['加做基因筛查', '多查一些遗传病,一两千元', 'dna']] },
{ k: 'yuezi', h: '月子打算怎么坐?', s: '坐月子是生娃前后最大的一笔', if: function (st) { return has(st, [2]); }, o: [
['家里人照顾', '家人做月子餐、帮带宝宝', 'family'], ['请月嫂到家', '26 天住家月嫂', 'nurse'],
['住月子中心', '28 天,吃住护理都包', 'bldg2']] },
{ k: 'recov', h: '产后修复打算怎么做?', s: '产后 42 天复查评估后再做,盆底肌锻炼可以早点开始', p: 5, if: function (st) { return has(st, [2]); }, o: [
['医院盆底康复', '医保能报一部分', 'hosp'],
['修复机构疗程', '盆底、腹直肌一起做', 'star'],
['修复加私教塑形', '一对一,做全套', 'gem']] },
{ k: 'cuiru', h: '涨奶、堵奶怎么办?', s: '催乳、通乳按次收费', p: 3, if: function (st) { return has(st, [2]); }, o: [
['自己和家人揉', '实在不行再找人', 'family'],
['请催乳师几次', '涨奶、堵奶时上门', 'nurse'],
['包个通乳套餐', '整个月子随叫随到', 'star']] },
{ k: 'swim', h: '宝宝抚触、游泳要去吗?', s: '新生儿抚触、婴儿游泳馆', p: 2, if: function (st) { return has(st, [2]); }, o: [
['在家自己做', '跟着视频学抚触', 'house'],
['偶尔去游几次', '游泳馆按次买', 'drop'],
['办卡定期去', '抚触、游泳、按摩办卡', 'star']] },
{ k: 'party', h: '满月、百天这些要办吗?', s: '孕妇照、满月酒、百天照,仪式感也是一笔', if: function (st) { return has(st, [0, 1, 2, 3]); }, o: [
['简单过', '自己拍拍照,家里吃顿饭', 'heart'], ['请亲友聚一聚', '拍套写真,摆几桌', 'cake'],
['办得隆重', '棚拍写真,满月、百天都办宴', 'camera']] },
{ k: 'feed', h: '宝宝打算怎么喂?', s: '第一年奶粉花多少,差别就在这', d: 1, if: function (st) { return has(st, [2, 3]); }, o: [
['母乳为主', '偶尔补一点奶粉', 'drop'], ['混合喂养', '母乳加奶粉', 'mix'], ['奶粉喂养', '主要喝奶粉', 'bottle']] },
{ k: 'gear', h: '宝宝用品怎么置办?', s: '待产包、推车、婴儿床、衣服玩具,档次差得多', if: function (st) { return has(st, [0, 1, 3, 4]); }, o: [
['能省则省', '亲戚送的、二手的接着用', 'recycle'], ['实用为主', '国产好牌子,够用就好', 'cart'],
['一步到位', '大牌、颜值高的直接买', 'gem']] },
{ k: 'diaper', h: '纸尿裤和洗护用哪种?', s: '要用到两三岁,一天好几片', p: 4, if: function (st) { return has(st, [3, 4]); }, o: [
['平价国产', '性价比高的牌子', 'cart'],
['国产大牌', '好用不红屁屁', 'star'],
['进口大牌', '日本、欧洲进口', 'globe']] },
{ k: 'food', h: '辅食和零食怎么准备?', s: '6 个月加辅食,之后吃得越来越多', p: 3, if: function (st) { return has(st, [3, 4]); }, o: [
['家里自己做', '米粉、菜泥自己蒸', 'house'],
['自己做加买点', '买米粉、果泥、宝宝零食', 'bowl'],
['买有机进口', '有机辅食、进口零食', 'gem']] },
{ k: 'vac', h: '自费疫苗打不打?', s: '免费疫苗之外,还有 13 价、五联、流感、手足口', p: 5, if: function (st) { return has(st, [3, 4]); }, o: [
['只打必要的', '免费的打齐,偶尔补一针', 'shield2'],
['挑几针打', '13 价、五联、流感这些', 'syringe'],
['能打的都打', '自费疫苗全打', 'gem']] },
{ k: 'care', h: '0~3 岁,白天谁带娃?', s: '产假结束后的安排,差别很大', if: function (st) { return has(st, [3, 4, 5]); }, o: [
['老人或自己带', '给老人一点补贴,3 岁上幼儿园', 'family'], ['家里带,2 岁上托班', '第 3 年交托班费', 'flag'],
['请育儿嫂', '住家或白班阿姨,带到上幼儿园', 'nurse'], ['1 岁半上托育园', '全日托,按月费算', 'school']] },
{ k: 'doc', h: '娃生病了一般去哪看?', s: '儿保体检、看病拿药,一年好几回', p: 4, if: function (st) { return has(st, [3, 4, 5, 6, 7, 8]); }, o: [
['公立医院儿科', '排队久一点,医保能报', 'hosp'],
['公立加特需门诊', '着急就挂特需', 'star'],
['私立儿科', '不用排队,费用自付', 'gem']] },
{ k: 'school', h: '上学打算走哪条路?', s: '幼儿园到高中,学费差距最大的一项', if: function (st) { return has(st, [5, 6, 7, 8]); }, o: [
['公立为主', '普惠园 + 就近公立学校', 'book'], ['民办私立', '民办园 + 民办中小学', 'cap'], ['国际学校', '双语园 + 国际学校', 'globe']] },
{ k: 'commute', h: '上学怎么接送?', s: '幼儿园到初中,天天都要跑', p: 2, if: function (st) { return has(st, [5, 6, 7]); }, o: [
['家人接送', '走路、电动车或公交', 'family'],
['校车或公交', '按学期交校车费', 'bus'],
['专车或校车', '专人接送、私校校车', 'car']] },
{ k: 'extra', h: '早教和课外班呢?', s: '早教、兴趣班、补习,一路算下去', if: function (st) { return has(st, [3, 4, 5, 6, 7, 8]); }, o: [
['顺其自然', '以玩为主,偶尔报一门', 'sprout'], ['适度报几门', '一两门兴趣 + 必要补习', 'music'], ['拼一把', '早教、兴趣、补习一个不落', 'rocket']] },
{ k: 'camp', h: '寒暑假怎么安排?', s: '托管班、夏令营、研学、游学', p: 3, if: function (st) { return has(st, [6, 7, 8]); }, o: [
['在家或托管班', '报个假期托管班', 'house'],
['夏令营和研学', '每年报一两个', 'flag'],
['海外游学', '出国研学营', 'globe']] },
{ k: 'trip', h: '一家人多久出去玩一次?', s: '游乐场、亲子游、家庭旅行', p: 3, if: function (st) { return has(st, [4, 5, 6, 7, 8]); }, o: [
['周边转转', '公园、游乐场为主', 'tree'],
['每年一两次旅行', '国内亲子游', 'car'],
['常出国玩', '每年出境游', 'plane']] },
{ k: 'cloth', h: '平时吃穿用度呢?', s: '吃饭、衣服鞋子,一年下来是大头', p: 2, if: function (st) { return has(st, [5, 6, 7, 8]); }, o: [
['朴素过日子', '吃饱穿暖,不讲牌子', 'house'],
['舒服体面', '常买新衣,吃得好一点', 'cart'],
['挑好的买', '有机食材、品牌童装', 'gem']] },
{ k: 'digi', h: '平板、手机、电脑买什么档?', s: '学习机、手表,到手机电脑', p: 2, if: function (st) { return has(st, [6, 7, 8]); }, o: [
['够用就行', '平价学习机、旧手机', 'recycle'],
['主流品牌', '学习平板、主流手机电脑', 'cart'],
['都买最新款', '一出新款就换', 'gem']] },
{ k: 'pocket', h: '零花钱给多少?', s: '从小学一直给到高中', p: 1, if: function (st) { return has(st, [6, 7, 8]); }, o: [
['少给一点', '一周几块到几十', 'coin'],
['跟同学差不多', '不多不少', 'cart'],
['宽裕些', '想买就买', 'gem']] },
{ k: 'gift', h: '生日和节日怎么过?', s: '生日礼物、节日、拍照', p: 1, if: function (st) { return has(st, [4, 5, 6, 7]); }, o: [
['简单过', '家里吃顿饭、买个蛋糕', 'cake'],
['用心准备', '礼物、拍照每年都有', 'star'],
['办派对', '生日派对、大礼物', 'gem']] },
{ k: 'ins', h: '给娃买哪些保险?', s: '从出生一直交到 18 岁,差别不小', if: function (st) { return has(st, [3, 4, 5, 6, 7, 8]); }, o: [
['只上少儿医保', '每年几百元,看病能报', 'shield2'], ['医保加商业医疗险', '再补一份百万医疗、意外险', 'umb'],
['高端医疗加重疾险', '私立医院能直付,重疾也保上', 'gem']] },
{ k: 'inc', type: 'income', h: '你家每月收入大概多少?', s: '填了能看看这张账单相当于你家几年的收入' }
];
var QK = {}; QS.forEach(function (q) { QK[q.k] = q; });
/* v9:带 p 的是分阶段细题,默认跟路线档次(量力 0 / 从容 1 / 富养 2);脐带血只有富养默认存,接送跟上学路线走 */
var TIER_DEF = { cord: [0, 0, 1], commute: [0, 0, 2] };
ROUTES.forEach(function (R, i) {
QS.forEach(function (q) { if (q.p && q.d == null && R.def[q.k] == null) R.def[q.k] = TIER_DEF[q.k] ? TIER_DEF[q.k][i] : i; });
});
var QMAX = 15; // 一次最多 15 题;超了先去掉 p 小的细题(同 p 去后面的),去掉的按默认算
var CITY_S = ['一线城市', '新一线/省会', '地级市', '县城乡镇'];
/* ================= 费用表(单位:元,2025~2026 国内常见公开行情;基准 = 新一线及省会) =================
I(名称, [量力低,高], [从容低,高], [富养低,高], 城市系数, 二胎复用系数)。名称可以是 [量力名, 从容名, 富养名];某档 [0,0] 表示这档不花这笔。
D(答案键, 名称或按答案的名称数组, [按答案的 [低,高]...], 城市系数):随答案变(医院 hosp / 上学路线 school / 课外班 extra 等)。
F(名称或 s=>名称, s=>[低,高], 城市系数, 二胎复用系数):要同时看几道题的项(分娩方式 × 医院、月子 × 档次、喂养 × 档次);名称为空或 [0,0] 不列。
城市系数:s 人工/服务/教育,g 吃穿用,m 医疗,0 不随城市变。 */
var CF = { s: [1.45, 1, 0.72, 0.55], g: [1.1, 1, 0.9, 0.82], m: [1.25, 1, 0.8, 0.65], '0': [1, 1, 1, 1] };
function I(n, a, b, c, k, r) { return { n: n, v: [a, b, c], k: k, r: r }; }
function G(g, n, a, b, c, k, r) { return { n: n, v: [a, b, c], k: k, r: r, gk: g }; } // 同 I,但档次跟某道题走(screen/gear/party/ins),不跟路线
function D(g, n, vs, k) { return { g: g, n: n, dv: vs, k: k }; }
function F(n, fv, k, r) { return { n: typeof n === 'function' ? n : function () { return n; }, fv: fv, k: k, r: r }; }
function sc3(v, f) { return [v[0] * f, v[1] * f]; }
// 分娩主项 [医院][分娩方式:顺产/剖宫产/还不确定](不确定 ≈ 顺产 + 约三成剖宫产差价)
var DELIV = [
[[3000, 6000], [8000, 15000], [4500, 9000]],
[[6000, 12000], [15000, 25000], [8500, 16000]],
[[25000, 50000], [45000, 90000], [31000, 62000]]
];
var YUEZI = [ // 月子主项 [月子打算][档次 T]
[[2000, 4000], [2500, 5000], [3000, 6000]],
[[8000, 13000], [12000, 20000], [18000, 30000]],
[[25000, 40000], [35000, 60000], [50000, 120000]]
];
var MILK1 = [[8000, 14000], [12000, 20000], [18000, 28000]], MILK_F = [0.15, 0.5, 1]; // 0~1 岁全奶粉喂养 [档次];按喂养方式乘系数
var SUB = [3600, 9600], NANNY = [60000, 90000], TUOBAN = [20000, 45000], DAYCARE = [27500, 55000];
var COST = [
// ① 孕期产检
[
D('hosp', ['建档和常规产检(约 12 次)', '特需门诊产检(约 12 次)', '私立医院产检套餐'], [[1500, 3000], [5000, 10000], [15000, 30000]], 'm'),
D('hosp', 'B 超(NT、大排畸、晚孕)', [[800, 1500], [1500, 3000], [2000, 4000]], 'm'),
G('screen', ['唐氏筛查', '无创 DNA(NIPT)', '无创 DNA 加强版'], [200, 400], [1500, 2500], [2500, 4000], 'm'),
D('hosp', '四维彩超', [[300, 600], [500, 1000], [800, 2000]], 'm'),
D('hosp', '糖耐量和血液检查', [[300, 600], [500, 1000], [800, 1500]], 'm'),
D('hosp', '胎心监护', [[200, 500], [400, 800], [800, 1500]], 'm'),
G('pnut', '孕妇奶粉', [0, 600], [1200, 2400], [2400, 4800], 'g'),
G('pnut', '叶酸、DHA、钙铁维生素', [500, 1200], [1500, 3000], [3000, 6000], 'g'),
G('momgear', '孕妇装和孕妇内衣', [600, 1500], [1500, 3500], [4000, 10000], 'g'),
G('momgear', '托腹带和孕期护腰', [150, 400], [400, 800], [800, 2000], 'g'),
G('momgear', '妊娠油和孕期护肤', [100, 300], [400, 1000], [1500, 4000], 'g'),
G('momgear', '孕妇枕、孕妇鞋', [150, 400], [300, 800], [800, 2000], 'g'),
G('pclass', ['孕妇学校(免费为主)', '孕期瑜伽和课程', '孕期私教和产前课程'], [0, 200], [1000, 3000], [5000, 12000], 's'),
G('ptrans', '产检交通和打车', [500, 1500], [1500, 3000], [3000, 6000], 's'),
G('party', ['孕妇照(自己拍为主)', '孕妇写真', '孕妇写真(棚拍)'], [0, 300], [1000, 3000], [3000, 8000], 's'),
G('gear', '待产包', [500, 1200], [1200, 2500], [2500, 5000], 'g', 0.6)
],
// ② 分娩住院
[
F(function (s) { return ['公立普通病房', '公立特需', '私立医院'][s.hosp] + ['顺产', '剖宫产', '分娩(顺剖未定)'][s.birth]; },
function (s) { return DELIV[s.hosp][s.birth]; }, 'm'),
F(function (s) { return s.birth === 1 ? '术后镇痛泵' : ['无痛分娩', '无痛分娩和导乐', '无痛和一对一助产'][s.hosp]; },
function (s) { return s.birth === 1 ? [[500, 1200], [800, 1500], [1000, 3000]][s.hosp] : [[1000, 2500], [2000, 4000], [3000, 8000]][s.hosp]; }, 'm'),
F(function (s) { return ['病房床位', '单间升级', 'VIP 套房升级'][s.hosp] + (s.birth === 1 ? '(多住几天)' : ''); },
function (s) { return sc3([[300, 800], [2000, 5000], [5000, 15000]][s.hosp], [1, 1.6, 1.2][s.birth]); }, 'm'),
G('stay', '陪护床和护工', [300, 800], [1000, 2500], [2000, 5000], 's'),
D('hosp', '新生儿筛查(听力、足跟血)', [[300, 800], [500, 1200], [1500, 3000]], 'm'),
F('新生儿基因筛查(加做)', function (s) { return s.nbs ? [1000, 2500] : [0, 0]; }, 'm'),
G('gear', '新生儿尿片、衣服、包被', [200, 500], [400, 800], [800, 1500], 'g'),
I('产妇用品(产褥垫、收腹带)', [200, 500], [400, 800], [800, 1500], 'g'),
F(function (s) { return ['住院餐', '住院营养餐', '院内营养月子餐'][s.hosp]; }, function (s) { return sc3([[300, 800], [800, 1500], [2000, 4000]][s.hosp], [1, 1.6, 1.2][s.birth]); }, 's'),
F('脐带血存储(自体库)', function (s) { return s.cord ? [15000, 25000] : [0, 0]; }, '0'),
G('party', '报喜红包和喜糖', [500, 1500], [1000, 3000], [3000, 8000], 'g'),
G('ptrans', '家属交通和餐费', [300, 800], [500, 1500], [1000, 3000], 's'),
F('生育保险报销(预估)', function (s) { return sc3([[-5000, -2500], [-4500, -2000], [-2500, -500]][s.hosp], [1, 1.4, 1.1][s.birth]); }, 'm')
],
// ③ 坐月子
[
F(function (s) { return ['月子餐食材(家人做)', '住家月嫂 26 天', s.T === 2 ? '高端月子中心 28 天' : '月子中心 28 天'][s.yuezi]; },
function (s) { return YUEZI[s.yuezi][s.T]; }, 's'),
F('月子餐食材和营养汤', function (s) { return s.yuezi === 1 ? [[1500, 3000], [2000, 4000], [3000, 6000]][s.T] : [0, 0]; }, 'g'),
F('催乳和通乳', function (s) { return s.feed === 2 ? [0, 0] : [[0, 500], [800, 2000], [2000, 5000]][s.cuiru]; }, 's'),
F('吸奶器和储奶袋', function (s) { return s.feed === 2 ? [0, 0] : [[300, 800], [800, 2000], [2000, 4000]][s.momgear]; }, 'g', 0.5),
F('哺乳内衣和防溢乳垫', function (s) { return s.feed === 2 ? [0, 0] : [[200, 500], [400, 900], [800, 2000]][s.momgear]; }, 'g'),
G('momgear', '产妇卫生巾和月子服', [300, 700], [500, 1200], [1000, 2500], 'g'),
G('recov', ['产后盆底康复(医院)', '产后修复疗程', '产后修复和私教塑形'], [500, 1500], [3000, 6000], [10000, 25000], 's'),
G('hosp', '产后 42 天复查', [200, 500], [400, 1000], [1000, 3000], 'm'),
G('hosp', '宝宝 42 天体检', [200, 500], [300, 800], [800, 2000], 'm'),
G('swim', '新生儿抚触和游泳', [0, 300], [600, 1500], [2000, 5000], 's'),
G('party', '新生儿摄影', [0, 500], [1000, 3000], [3000, 8000], 's'),
G('party', ['满月家宴和喜糖', '满月酒和喜糖', '满月宴和回礼'], [2000, 6000], [5000, 15000], [15000, 40000], 'g')
],
// ④ 0~1 岁
[
F(function (s) { return ['国产奶粉', '国产高端奶粉', '进口有机奶粉'][s.T] + ['(偶尔补充)', '(混合喂养)', '(全奶粉喂养)'][s.feed]; },
function (s) { return sc3(MILK1[s.T], MILK_F[s.feed]); }, 'g'),
G('food', '米粉和辅食', [800, 1500], [1500, 3000], [3000, 6000], 'g'),
G('gear', '奶瓶和奶嘴', [200, 400], [400, 800], [800, 1500], 'g'),
G('gear', '温奶器、消毒器', [200, 400], [400, 900], [1000, 2500], 'g', 0.5),
G('gear', '辅食机、加湿器等小家电', [300, 800], [800, 2000], [2000, 5000], 'g', 0.6),
G('diaper', '纸尿裤', [2000, 3200], [3200, 4800], [4800, 7200], 'g'),
G('diaper', '湿巾和棉柔巾', [400, 700], [600, 1000], [1000, 1800], 'g'),
G('diaper', '隔尿垫和口水巾', [100, 250], [200, 400], [400, 800], 'g'),
G('diaper', '洗护用品和护臀膏', [300, 600], [600, 1200], [1500, 3000], 'g'),
G('gear', '婴儿床和床品', [600, 1500], [1500, 3500], [5000, 12000], 'g', 0.4),
G('gear', '推车', [300, 800], [1500, 3500], [4000, 9000], 'g', 0.4),
G('gear', '安全座椅', [400, 900], [1200, 2500], [3000, 6000], 'g', 0.5),
G('gear', '背带和腰凳', [100, 300], [300, 800], [1000, 2500], 'g', 0.5),
G('gear', '衣服鞋袜', [1000, 2000], [2000, 4000], [5000, 10000], 'g', 0.7),
G('gear', '包被和睡袋', [150, 400], [400, 800], [1000, 2500], 'g', 0.6),
G('gear', '玩具和绘本', [400, 1000], [1000, 2500], [3000, 8000], 'g', 0.7),
D('extra', ['亲子游戏(在家)', '亲子早教体验课', '亲子早教年卡'], [[0, 500], [2000, 5000], [10000, 20000]], 's'),
G('vac', ['自费疫苗(少量)', '自费疫苗(13 价、五联等)', '自费疫苗全打'], [500, 2000], [4000, 6000], [6000, 9000], '0'),
G('doc', ['儿保体检', '儿保体检', '私立儿科体检'], [200, 500], [500, 1200], [2000, 5000], 'm'),
G('doc', ['看病和医药', '看病和医药', '私立儿科看病'], [600, 2000], [1500, 3000], [4000, 10000], 'm'),
G('ins', ['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [400, 600], [800, 2000], [8000, 15000], '0'),
G('party', '百天照、周岁照', [0, 600], [1000, 3000], [3000, 8000], 's'),
G('party', ['百天、周岁家宴', '百天宴、周岁宴', '百天宴、周岁宴'], [1000, 4000], [4000, 10000], [10000, 30000], 'g'),
{ g: 'care', n: function (s) { return s.care === 2 ? '育儿嫂(产假后约 8 个月)' : '老人帮带补贴'; },
fv: function (s) { return s.care === 2 ? [40000, 60000] : [SUB[0] * 0.6, SUB[1] * 0.6]; }, k: 's' }
],
// ⑤ 1~3 岁(f1 = 属于 1~2 岁那一年的比例,用来算 2 岁累计)
[
F(function (s) { return ['国产奶粉', '国产高端奶粉', '进口高端奶粉'][s.T]; }, function (s) { return sc3([[3000, 6000], [6000, 10000], [12000, 18000]][s.T], [0.7, 0.85, 1][s.feed]); }, 'g'),
G('diaper', '纸尿裤和拉拉裤', [2000, 3500], [3000, 5000], [4500, 7000], 'g'),
G('diaper', '湿巾和洗护用品', [600, 1200], [1000, 2000], [2000, 4000], 'g'),
G('food', '辅食、水果和零食', [4000, 8000], [6000, 12000], [12000, 24000], 'g'),
G('gear', '餐椅和儿童餐具', [200, 500], [500, 1200], [1500, 3500], 'g', 0.5),
G('gear', '衣服鞋袜', [2000, 4000], [4000, 8000], [10000, 20000], 'g', 0.8),
G('gear', '玩具和积木', [800, 2000], [2000, 5000], [6000, 15000], 'g', 0.8),
I('绘本', [300, 800], [800, 2000], [2000, 5000], 'g', 0.8),
G('gear', '滑板车、平衡车', [200, 500], [500, 1200], [1500, 4000], 'g', 0.6),
G('gear', '防撞角、护栏等安全用品', [100, 300], [300, 800], [800, 2000], 'g', 0.5),
G('vac', ['自费疫苗(流感等)', '自费疫苗', '自费疫苗全打'], [300, 800], [1500, 3000], [3000, 5000], '0'),
G('doc', ['儿保体检', '儿保体检', '私立儿科体检'], [300, 600], [600, 1500], [3000, 8000], 'm'),
G('doc', ['看病和医药', '看病和医药', '私立儿科看病'], [1000, 3000], [2000, 5000], [6000, 15000], 'm'),
G('ins', ['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [800, 1200], [1600, 4000], [16000, 30000], '0'),
D('extra', ['绘本和遛娃', '早教班(每周 1~2 节)', '早教和游泳、音乐等兴趣班'], [[500, 2000], [8000, 16000], [25000, 50000]], 's'),
G('trip', '游乐场和亲子游', [1000, 3000], [4000, 8000], [15000, 30000], '0'),
G('gift', '理发、照片和生日', [500, 1500], [1500, 4000], [4000, 10000], 's'),
{ g: 'care', n: function (s) { return ['老人帮带补贴(2 年)', '老人帮带和 2 岁托班', '育儿嫂工资(2 年)', '托育园(1 岁半起)'][s.care]; },
fy: function (s) {
if (s.care === 0) return [SUB, SUB];
if (s.care === 1) return [SUB, [TUOBAN[0] + SUB[0] / 2, TUOBAN[1] + SUB[1] / 2]];
if (s.care === 2) return [NANNY, NANNY];
return [[DAYCARE[0] / 2 + SUB[0] / 2, DAYCARE[1] / 2 + SUB[1] / 2], DAYCARE];
}, k: 's' }
],
// ⑥ 幼儿园 3~6 岁
[
// 2026-11-08:国办《关于逐步推行免费学前教育的意见》(2025-08)2025 秋季学期起免公办园大班保教费、民办园按公办标准减免 → 普惠按 2 年算,民办/国际减掉约一年公办保教费
D('school', ['普惠幼儿园保教费(大班免,2025 秋起)', '民办幼儿园学费(大班有减免)', '双语/国际幼儿园学费(大班有减免)'], [[10000, 24000], [55000, 108000], [175000, 348000]], 's'),
D('school', '伙食费', [[8000, 14000], [12000, 20000], [20000, 40000]], 's'),
I('入园用品和被褥', [300, 800], [800, 1500], [1500, 3000], 'g'),
I('园服', [300, 800], [800, 1500], [1500, 3000], 'g'),
I('园内活动和材料费', [500, 1500], [1500, 3000], [3000, 8000], 's'),
D('commute', ['家人接送', '校车或公交', '专车或校车'], [[1000, 3000], [6000, 12000], [12000, 24000]], 's'),
D('extra', ['美术或音乐(偶尔)', '美术或音乐兴趣班', '钢琴、美术、舞蹈'], [[1500, 4000], [8000, 16000], [25000, 50000]], 's'),
D('extra', ['户外玩耍', '游泳或轮滑', '游泳、马术、网球'], [[0, 1000], [6000, 12000], [20000, 40000]], 's'),
D('extra', ['英语绘本和动画', '英语启蒙班', '外教英语'], [[300, 800], [6000, 12000], [20000, 40000]], 's'),
G('cloth', '日常饮食和零食', [9000, 18000], [18000, 30000], [36000, 72000], 'g'),
G('cloth', '衣服鞋袜', [3000, 6000], [6000, 12000], [15000, 30000], 'g'),
I('玩具和绘本', [2000, 4000], [4000, 8000], [10000, 20000], 'g'),
G('doc', ['看病和医药', '看病和医药', '私立儿科看病'], [2000, 4000], [4000, 8000], [12000, 25000], 'm'),
G('ins', ['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [1200, 1800], [2400, 6000], [24000, 45000], '0'),
G('trip', ['周边游玩', '每年一两次旅行', '出境亲子游'], [3000, 8000], [12000, 24000], [45000, 90000], '0'),
G('gift', '生日和节日礼物', [600, 1500], [1500, 4000], [5000, 12000], 'g'),
I('理发和日用品', [300, 800], [800, 1500], [1500, 3000], 's'),
{ g: 'care', n: function (s) { return s.care === 2 ? '住家阿姨(3 年)' : '老人接送补贴'; },
fv: function (s) { return s.care === 2 ? [120000, 200000] : [6000, 18000]; }, k: 's' }
],
// ⑦ 小学 6~12 岁
[
D('school', ['学杂费和代收费', '民办小学学费', '国际学校学费'], [[1500, 4000], [180000, 360000], [600000, 1200000]], 's'),
I('校服', [600, 1500], [1500, 3000], [3000, 6000], 'g'),
I('课本、教辅和文具', [3000, 6000], [5000, 10000], [8000, 15000], 'g'),
I('书包和学习桌椅', [800, 2000], [2000, 4000], [4000, 10000], 'g'),
D('school', ['午餐', '餐费', '餐费'], [[9000, 15000], [15000, 24000], [24000, 40000]], 's'),
D('school', ['课后托管和延时服务', '课后托管', ''], [[3000, 8000], [2000, 5000], [0, 0]], 's'),
D('commute', ['上下学交通', '校车或公交', '专车或校车'], [[2000, 6000], [8000, 15000], [15000, 30000]], 's'),
D('extra', ['学科辅导(偶尔)', '学科辅导(语数英)', '学科一对一辅导'], [[3000, 10000], [30000, 60000], [80000, 160000]], 's'),
D('extra', ['美术或音乐', '美术、音乐兴趣班', '钢琴、美术、舞蹈'], [[3000, 8000], [15000, 30000], [50000, 100000]], 's'),
D('extra', ['跳绳、游泳', '游泳、球类', '网球、马术、击剑'], [[2000, 6000], [12000, 24000], [40000, 80000]], 's'),
D('extra', ['', '编程或英语', '编程、竞赛和外教'], [[0, 0], [5000, 12000], [30000, 60000]], 's'),
G('camp', ['假期托管班', '夏令营和研学', '海外游学和研学营'], [1000, 4000], [10000, 24000], [50000, 120000], '0'),
G('digi', '平板、手表、学习机', [1500, 3000], [3000, 6000], [8000, 15000], 'g'),
G('cloth', '日常饮食', [24000, 36000], [36000, 60000], [72000, 120000], 'g'),
G('cloth', '衣服鞋袜', [6000, 12000], [12000, 24000], [30000, 60000], 'g'),
G('pocket', '零花钱', [1000, 3000], [3000, 8000], [8000, 20000], '0'),
G('doc', ['看病和医药', '看病和医药', '私立医疗'], [3000, 6000], [6000, 12000], [20000, 40000], 'm'),
I('近视防控和看牙', [1000, 3000], [3000, 8000], [10000, 25000], 'm'),
G('ins', ['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [2400, 3600], [4800, 12000], [48000, 90000], '0'),
G('trip', ['假期游玩', '家庭旅行', '出境家庭游'], [5000, 12000], [20000, 40000], [80000, 160000], '0'),
G('gift', '生日和节日', [1200, 3000], [3000, 8000], [10000, 24000], 'g')
],
// ⑧ 初中 12~15 岁
[
D('school', ['学杂费和代收费', '民办初中学费', '国际学校学费'], [[800, 2500], [60000, 150000], [300000, 600000]], 's'),
I('校服', [400, 1000], [800, 1500], [1500, 3000], 'g'),
I('课本、教辅和文具', [2000, 4000], [3000, 6000], [5000, 10000], 'g'),
D('school', ['午餐或住宿', '住宿和伙食', '住宿、伙食和活动'], [[6000, 12000], [12000, 24000], [24000, 45000]], 's'),
D('commute', ['上下学交通', '校车或公交', '专车或校车'], [[1000, 3000], [3000, 8000], [8000, 15000]], 's'),
D('extra', ['必要的补习', '学科补习', '学科一对一补习'], [[6000, 18000], [36000, 72000], [100000, 200000]], 's'),
D('extra', ['', '艺术或体育特长', '艺术、体育特长和竞赛'], [[0, 0], [8000, 18000], [30000, 60000]], 's'),
D('extra', ['中考体育(自己练)', '中考体育训练', '中考体育私教'], [[0, 1000], [2000, 5000], [5000, 10000]], 's'),
G('camp', ['假期游玩', '研学和夏令营', '海外游学'], [1000, 4000], [8000, 18000], [30000, 80000], '0'),
G('digi', '手机和电脑', [2000, 4000], [4000, 8000], [10000, 20000], 'g'),
G('cloth', '日常饮食', [15000, 24000], [24000, 36000], [45000, 75000], 'g'),
G('cloth', '衣服鞋子', [4000, 8000], [8000, 15000], [20000, 40000], 'g'),
G('pocket', '零花钱', [1500, 4000], [4000, 9000], [10000, 25000], '0'),
G('doc', ['看病和医药', '看病和医药', '私立医疗'], [1500, 3000], [3000, 6000], [10000, 20000], 'm'),
I('眼镜和牙齿', [1500, 4000], [4000, 10000], [15000, 30000], 'm'),
G('ins', ['医保', '医保和医疗险', '高端医疗险和重疾险'], [1200, 1800], [2400, 6000], [24000, 45000], '0'),
G('trip', ['周边游玩', '家庭旅行', '出境家庭游'], [3000, 8000], [10000, 20000], [40000, 80000], '0'),
G('gift', '生日和节日', [600, 1500], [1500, 4000], [5000, 12000], 'g')
],
// ⑨ 高中 15~18 岁
[
D('school', ['公立高中学费', '民办高中学费', '国际高中学费'], [[3000, 9000], [60000, 150000], [400000, 800000]], 's'),
D('school', '住宿费', [[2000, 4500], [4000, 9000], [9000, 18000]], 's'),
D('school', '伙食费', [[12000, 21000], [15000, 27000], [27000, 54000]], 's'),
I('校服和床品', [500, 1200], [1000, 2000], [2000, 4000], 'g'),
I('课本、教辅和资料', [3000, 6000], [4000, 8000], [6000, 12000], 'g'),
D('extra', ['必要的补习', '学科补习', '学科一对一补习'], [[6000, 24000], [36000, 80000], [120000, 240000]], 's'),
D('extra', ['', '艺术或体育特长', '艺术、体育特长'], [[0, 0], [6000, 15000], [30000, 60000]], 's'),
D('extra', ['', '竞赛和强基辅导', '托福、SAT 和竞赛'], [[0, 0], [3000, 8000], [50000, 120000]], 's'),
G('camp', ['假期游玩', '研学', '海外游学'], [1000, 3000], [5000, 12000], [30000, 80000], '0'),
G('digi', '电脑和电子设备', [2000, 5000], [5000, 10000], [10000, 20000], 'g'),
I('交通和回家路费', [1000, 3000], [2000, 5000], [5000, 12000], 's'),
G('cloth', '在家饮食和营养', [9000, 15000], [15000, 24000], [30000, 50000], 'g'),
G('cloth', '衣服鞋子', [4000, 8000], [8000, 15000], [20000, 40000], 'g'),
G('pocket', '零花钱', [3000, 6000], [6000, 12000], [15000, 30000], '0'),
G('doc', ['看病和医药', '看病和医药', '私立医疗'], [1500, 3000], [3000, 6000], [10000, 20000], 'm'),
G('ins', ['医保', '医保和医疗险', '高端医疗险和重疾险'], [1200, 1800], [2400, 6000], [24000, 45000], '0'),
I(['高考和志愿填报', '高考、志愿和升学咨询', '留学申请和背景提升'], [1000, 3000], [3000, 10000], [50000, 150000], '0'),
G('trip', '成人礼和毕业旅行', [1000, 3000], [3000, 8000], [10000, 30000], '0')
]
];
function college(s) { // 如果还上大学(不计入合计)
if (s.school === 2) return { n: '海外本科', v: [1000000, 3000000] }; // 11-08 与教育金页统一:约 100~300 万,看国家
var v = [[60000, 100000], [100000, 180000], [160000, 300000]][s.T], f = [1.2, 1, 0.95, 0.9][s.city];
return { n: '国内本科 4 年', v: [v[0] * f, v[1] * f] };
}
var KEY = 'mmbox.huafei.v5', KEY4 = 'mmbox.huafei.v4';
var $ = function (s) { return document.querySelector(s); };
var sc = $('#scroll');
var reduce = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches;
var route = -1, A = {}, cur = 0, hasBill = false, bill = null, billNo = '', billAt = '';
/* ================= 计算 ================= */
function r100(n) { return Math.round(n / 100) * 100; }
function sel() { return A.st && A.st.length ? A.st : ALL; }
function ctx(r, a) {
var d = ROUTES[r].def;
function g(k) { return a[k] != null ? a[k] : d[k] != null ? d[k] : QK[k].d; }
var o = { T: tierOf(r), city: a.city == null ? 1 : a.city, kid: a.kid || 0,
hosp: g('hosp'), birth: g('birth'), yuezi: g('yuezi'), feed: g('feed'),
care: g('care'), school: g('school'), extra: g('extra'),
screen: g('screen'), gear: g('gear'), party: g('party'), ins: g('ins') };
QS.forEach(function (q) { if (q.p) o[q.k] = g(q.k); });
return o;
}
function calc(r, a, stages) {
var s = ctx(r, a);
stages = stages || ALL;
var segs = COST.map(function (items, si) {
var it = [];
items.forEach(function (d) {
var f = CF[d.k][s.city] * (s.kid && d.r ? d.r : 1), name, v, y = null;
if (d.dv) { var ai = s[d.g]; v = d.dv[ai]; name = Array.isArray(d.n) ? d.n[ai] : d.n; }
else if (d.fy) { y = d.fy(s); v = [y[0][0] + y[1][0], y[0][1] + y[1][1]]; name = d.n(s); }
else if (d.fv) { v = d.fv(s); name = d.n(s); }
else { var ti = d.gk ? s[d.gk] : s.T; v = d.v[ti]; name = Array.isArray(d.n) ? d.n[ti] : d.n; }
if (!name || (!v[0] && !v[1])) return;
var lo = v[0] * f, hi = v[1] * f, m = r100((lo + hi) / 2);
var m1 = y ? r100((y[0][0] + y[0][1]) / 2 * f) : r100(m * 0.55); // 1~3 岁里属于 1~2 岁的部分
it.push({ n: name, lo: lo, hi: hi, m: m, m1: m1 });
});
var o = { i: si, t: STAGES[si][0], a: STAGES[si][1], it: it, lo: 0, hi: 0, m: 0 };
it.forEach(function (x) { o.lo += x.lo; o.hi += x.hi; o.m += x.m; });
return o;
}).filter(function (sg) { return stages.indexOf(sg.i) >= 0; });
var tot = { segs: segs, lo: 0, hi: 0, m: 0, s: s, months: 0, n: 0 };
segs.forEach(function (x) { tot.lo += x.lo; tot.hi += x.hi; tot.m += x.m; tot.months += STAGES[x.i][4]; tot.n += x.it.length; });
// 1/2/3 岁累计:只累计所选阶段,且只在含 0~1 / 1~3 岁时显示
var acc = 0;
segs.forEach(function (x) {
if (x.i <= 3) acc += x.m;
if (x.i === 3) tot.n1 = acc;
if (x.i === 4) { tot.n2 = acc + x.it.reduce(function (a2, y) { return a2 + y.m1; }, 0); tot.n3 = acc + x.m; }
});
tot.col = college(s);
return tot;
}
/* ================= 格式 ================= */
function money(n) { var neg = n < 0; n = Math.abs(Math.round(n)); return (neg ? '-' : '') + String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
function w(n) {
var v = n / 10000;
if (v < 10) return (Math.round(v * 10) / 10) + '';
if (v < 100) return Math.round(v) + '';
return Math.round(v / 10) * 10 + '';
}
function wr(lo, hi) { return w(lo) + '~' + w(hi) + ' 万'; }
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
// 阶段范围文案:全部 → 怀孕到 18 岁;连续 3 段以上 → 从 A 到 B;否则 A + B
function rangeName(st) {
if (st.length === ALL.length) return '怀孕到 18 岁';
var names = st.map(function (i) { return STAGES[i][2]; });
var cont = st.every(function (x, k) { return !k || x === st[k - 1] + 1; });
if (cont && st.length >= 3) return '从' + names[0] + '到' + names[names.length - 1];
return names.join(' + ');
}
// 账单合计相当于几个月 / 几年收入
function incomeOf(b) {
var inc = +A.inc || 0; if (!inc) return null;
var mo = b.m / inc, avg = b.months ? b.m / b.months : 0, pct = avg / inc * 100;
return { inc: inc, mo: mo, txt: mo >= 24 ? (Math.round(mo / 12 * 10) / 10) + ' 年' : (mo >= 10 ? Math.round(mo) : Math.round(mo * 10) / 10) + ' 个月', avg: avg, pct: pct };
}
/* ================= 屏切换 ================= */
/* 11-03 用户:「点重新测试回到开始页,退回首页再进来,显示的还是账单」→ 记住最后停在哪一屏(开始 / 第几题 / 账单),再进来回到那一屏 */
var scr = 'sStart', booting = true;
function show(id) {
scr = id;
if (!booting) save();
[].forEach.call(document.querySelectorAll('.screen'), function (el) { el.classList.toggle('on', el.id === id); });
$('#bgTop').style.display = id === 'sQ' ? 'none' : '';
sc.scrollTop = 0;
}
/* ================= 开始屏(结果揭晓前全程不出现金额) ================= */
function renderStart() {
$('#routes').innerHTML = ROUTES.map(function (R, i) {
return '<button class="rt" data-r="' + i + '">' +
'<span class="ok"><svg class="ic"><use href="#i-check" xlink:href="#i-check"/></svg></span>' +
'<span class="top"><span class="no num">0' + (i + 1) + '</span>' +
'<span class="nm"><b>' + R.who + ' · <i>' + R.how + '</i></b><small>' + R.line + '</small></span>' +
'<svg class="bd" viewBox="0 0 56 56"><use href="#' + R.badge + '" xlink:href="#' + R.badge + '"/></svg></span>' +
'<span class="tags">' + R.tags.map(function (x) { return '<span>' + x + '</span>'; }).join('') + '</span></button>';
}).join('');
markRoute();
$('#goLast').classList.toggle('on', hasBill);
}
function markRoute() {
[].forEach.call(document.querySelectorAll('.rt'), function (b, i) { b.classList.toggle('on', i === route); });
var go = $('#goQ');
go.disabled = route < 0;
go.innerHTML = route < 0 ? '先选一条路线' : '就走 0' + (route + 1) + '「' + ROUTES[route].how + '」,开始答题<svg class="ic"><use href="#i-right" xlink:href="#i-right"/></svg>';
}
$('#routes').addEventListener('click', function (e) {
var b = e.target.closest('.rt'); if (!b) return;
var r = +b.dataset.r;
if (r !== route) { route = r; Object.keys(ROUTES[r].def).forEach(function (k) { delete A[k]; }); } // 换路线:后几题回到新路线的默认
markRoute();
});
$('#goQ').addEventListener('click', function () { if (route < 0) return; cur = 0; renderQ(); });
$('#goLast').addEventListener('click', function () { renderRes(false); });
/* ================= 问题屏 ================= */
function flow() {
var st = sel(), list = QS.filter(function (q) { return !q.if || q.if(st); });
if (list.length <= QMAX) return list;
var drop = list.filter(function (q) { return q.p; })
.sort(function (x, y) { return x.p - y.p || list.indexOf(y) - list.indexOf(x); })
.slice(0, list.length - QMAX);
return list.filter(function (q) { return drop.indexOf(q) < 0; });
}
function val(k) { if (A[k] != null) return A[k]; var d = ROUTES[route].def; return d[k] != null ? d[k] : QK[k].d != null ? QK[k].d : null; }
var stTmp = null, incTmp = '';
function renderQ() {
var list = flow(), q = list[cur], R = ROUTES[route], v = val(q.k), last = cur === list.length - 1;
$('#rp').innerHTML = '<svg viewBox="0 0 56 56"><use href="#' + R.badge + '" xlink:href="#' + R.badge + '"/></svg>' + R.how;
$('#dots').innerHTML = list.map(function (x, i) { return '<i class="' + (i < cur ? 'd' : i === cur ? 'c' : '') + '"></i>'; }).join('');
$('#qn').innerHTML = '第 <b class="num">' + (cur + 1) + '</b> / ' + list.length + ' 题';
$('#qh').textContent = q.h;
$('#qs').textContent = q.s;
var opts = $('#opts'), qx = $('#qx'), keep = $('#keep');
opts.innerHTML = ''; qx.innerHTML = '';
keep.classList.remove('on');
if (q.type === 'stage') {
stTmp = (A.st && A.st.length ? A.st : ALL).slice();
opts.className = 'opts hide2';
renderStage();
$('#qtip').textContent = '可多选;默认算全部';
} else if (q.type === 'income') {
opts.className = 'opts hide2';
incTmp = A.inc ? String(A.inc) : '';
qx.innerHTML = '<label class="inc"><span>¥</span><input id="incIn" type="number" inputmode="numeric" pattern="[0-9]*" placeholder="填家庭税后月收入" value="' + incTmp + '"><em>元/月</em></label>' +
'<div class="qk" id="qk">' + [5000, 10000, 20000, 30000, 50000].map(function (n) { return '<button data-n="' + n + '">' + (n < 10000 ? n : n / 10000 + ' 万') + '</button>'; }).join('') + '</div>' +
'<p class="priv"><svg class="ic"><use href="#o-shield2" xlink:href="#o-shield2"/></svg>只存在你的手机上,不上传、不保存到服务器</p>' +
'<button class="btn qgo" id="incGo">出账单<svg class="ic"><use href="#i-right" xlink:href="#i-right"/></svg></button>' +
'<button class="skip2" id="incSkip">跳过,不填收入</button>';
markInc();
$('#qtip').textContent = '';
} else {
opts.className = 'opts';
var rd = ROUTES[route].def[q.k], dTag = rd != null ? '路线默认' : '默认', dv = rd != null ? rd : q.d;
opts.innerHTML = q.o.map(function (o, i) {
var isDef = dv === i;
return '<button class="opt' + (v === i ? ' on' : '') + '" data-v="' + i + '"><span class="oi"><svg class="ic"><use href="#o-' + o[2] + '" xlink:href="#o-' + o[2] + '"/></svg></span>' +
'<span class="ox"><b>' + o[0] + (isDef ? '<em>' + dTag + '</em>' : '') + '</b><small>' + o[1] + '</small></span><span class="rd"></span></button>';
}).join('');
keep.classList.toggle('on', v != null);
$('#keepT').textContent = last ? '就这样,出账单' : (A[q.k] == null && dv != null ? (rd != null ? '按路线默认,下一题' : '按默认,下一题') : '就这样,下一题');
$('#qtip').textContent = '选完自动下一题';
}
$('#prevT').textContent = cur > 0 ? '上一题' : '换路线';
show('sQ');
}
function renderStage() {
var all = stTmp.length === ALL.length;
$('#qx').innerHTML = '<div class="stg"><button class="all' + (all ? ' on' : '') + '" data-a="1"><svg class="ic"><use href="#i-leaf" xlink:href="#i-leaf"/></svg>' +
'<span><b>全部(怀孕到 18 岁)</b><small>9 个阶段,一张长长的账单</small></span><span class="ck"><svg class="ic"><use href="#i-check" xlink:href="#i-check"/></svg></span></button>' +
'<div class="sgrid">' + STAGES.map(function (S, i) {
return '<button class="sc' + (stTmp.indexOf(i) >= 0 ? ' on' : '') + '" data-i="' + i + '"><i class="gi num">0' + (i + 1) + '</i><b>' + S[2] + '</b><small>' + S[3] + '</small>' +
'<span class="ck"><svg class="ic"><use href="#i-check" xlink:href="#i-check"/></svg></span></button>';
}).join('') + '</div>' +
'<p class="shint">' + (all ? '想只算某一段?直接点那一段就行,可以多选。' : '已选:' + rangeName(stTmp)) + '</p>' +
'<button class="btn qgo" id="stGo"' + (stTmp.length ? '' : ' disabled') + '>' + (stTmp.length ? '好了,下一题' : '至少选一段') + '<svg class="ic"><use href="#i-right" xlink:href="#i-right"/></svg></button></div>';
}
function markInc() {
var n = +incTmp || 0;
[].forEach.call(document.querySelectorAll('#qk button'), function (b) { b.classList.toggle('on', +b.dataset.n === n); });
var go = $('#incGo'); if (go) go.disabled = !(n >= 100);
}
$('#qx').addEventListener('click', function (e) {
var t = e.target.closest('button'); if (!t) return;
if (t.dataset.a) { stTmp = stTmp.length === ALL.length ? [] : ALL.slice(); renderStage(); return; }
if (t.dataset.i != null) {
var i = +t.dataset.i;
if (stTmp.length === ALL.length) stTmp = [i]; // 全选状态下点某一段 = 只算这一段
else if (stTmp.indexOf(i) >= 0) stTmp = stTmp.filter(function (x) { return x !== i; });
else stTmp = stTmp.concat(i).sort(function (a, b) { return a - b; });
renderStage(); return;
}
if (t.id === 'stGo') { if (!stTmp.length) return; A.st = stTmp.slice(); next(); return; }
if (t.dataset.n) { incTmp = t.dataset.n; $('#incIn').value = incTmp; markInc(); return; }
if (t.id === 'incGo') { var n = Math.round(+incTmp || 0); if (n < 100) return; A.inc = Math.min(n, 99999999); next(); return; }
if (t.id === 'incSkip') { A.inc = 0; next(); }
});
$('#qx').addEventListener('input', function (e) {
if (e.target.id !== 'incIn') return;
incTmp = e.target.value.replace(/[^\d]/g, '').slice(0, 8); markInc();
});
$('#qx').addEventListener('keydown', function (e) { if (e.target.id === 'incIn' && e.key === 'Enter') { e.target.blur(); var g = $('#incGo'); if (g && !g.disabled) g.click(); } });
function next() {
var list = flow();
if (cur < list.length - 1) { cur++; renderQ(); }
else { hasBill = true; newBill(); save(); renderRes(true); }
}
$('#opts').addEventListener('click', function (e) {
var b = e.target.closest('.opt'); if (!b || this.dataset.lock) return;
[].forEach.call(this.children, function (x) { x.classList.toggle('on', x === b); });
A[flow()[cur].k] = +b.dataset.v;
this.dataset.lock = '1';
var self = this;
setTimeout(function () { delete self.dataset.lock; next(); }, 260);
});
$('#keep').addEventListener('click', function () { var q = flow()[cur]; if (val(q.k) == null) return; A[q.k] = val(q.k); next(); });
$('#prev').addEventListener('click', function () {
if (cur > 0) { cur--; renderQ(); } else { renderStart(); show('sStart'); }
});
/* ================= 结果屏:账单 ================= */
function pad(n) { return (n < 10 ? '0' : '') + n; }
function newBill() {
var d = new Date();
billAt = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
var h = 7; (route + JSON.stringify(A) + d.getTime()).split('').forEach(function (c) { h = (h * 31 + c.charCodeAt(0)) % 100000; });
billNo = 'HF' + String(d.getFullYear()).slice(2) + pad(d.getMonth() + 1) + pad(d.getDate()) + '-' + ('0000' + h).slice(-5);
}
function barcode(seed) {
var x = 0, h = '', s = seed || 1;
while (x < 236) {
s = (s * 9301 + 49297) % 233280;
var bw = 1 + (s % 3), gap = 1 + ((s >> 3) % 3);
h += '<rect x="' + x + '" y="0" width="' + bw + '" height="40"/>';
x += bw + gap;
}
return '<svg class="bc" viewBox="0 0 236 40" preserveAspectRatio="none" fill="#2B2F36" aria-hidden="true">' + h + '</svg>';
}
function renderRes(animate) {
var R = ROUTES[route], st = sel(), b = calc(route, A, st), s = b.s, isAll = st.length === ALL.length;
var full = isAll ? b : calc(route, A, ALL);
bill = b; bill.full = full; bill.range = rangeName(st); bill.isAll = isAll;
var inc = incomeOf(b);
$('#rRoute').textContent = R.who.replace('我是', '') + ' · ' + R.how;
$('#rMeta').textContent = CITY_S[s.city] + (has(st, [0, 1, 2, 3, 4]) ? ' · ' + (s.kid ? '二胎' : '头胎') : '');
$('#rTitle').innerHTML = isAll ? '养到 18 岁,大约 <em>' + w(b.m) + ' 万</em>' : esc(bill.range) + ',合计约 <em>' + w(b.m) + ' 万</em>';
$('#rSub').textContent = '共 ' + b.segs.length + ' 个阶段、' + b.n + ' 项明细,单项取行情中间值';
var h = '<div class="rc-h"><div class="rc-logo"><svg class="ic"><use href="#i-leaf" xlink:href="#i-leaf"/></svg>兜爸心选 · 养娃账单</div>' +
'<div class="rc-sub">' + (isAll ? '从怀孕一直算到 18 岁' : esc(bill.range)) + '</div></div>' +
'<div class="rc-meta mono">' +
'<div><span>账单号</span><span>' + billNo + '</span></div>' +
'<div><span>日期</span><span>' + billAt + '</span></div>' +
'<div><span>范围</span><span>' + esc(bill.range) + ' · ' + b.n + ' 项</span></div>' +
'<div><span>收银员</span><span>兜爸</span></div></div>' +
'<div class="hr s"></div>' +
'<div class="li mono" style="font-size:11.5px;color:#86909C"><span class="n">项目</span><span class="d" style="border:0"></span><span class="v">金额(元)</span></div>';
b.segs.forEach(function (sg) {
h += '<div class="hr"></div><div class="rc-sec"><div class="rc-st"><b><i class="mono">0' + (sg.i + 1) + '</i>' + sg.t + '</b><span>' + sg.a + ' · ' + sg.it.length + ' 项</span></div>';
sg.it.forEach(function (x) {
h += '<div class="li' + (x.m < 0 ? ' neg' : '') + '"><span class="n">' + esc(x.n) + '</span><span class="d"></span><span class="v mono">' + money(x.m) + '</span></div>';
});
h += '<div class="sub"><span>小计</span><span class="v mono">' + money(sg.m) + '</span></div></div>';
});
h += '<div class="hr dbl"></div>' +
'<div class="rc-tot"><span>合计</span><b class="mono">¥' + money(b.m) + '</b></div>' +
'<div class="rc-rg">约 <em>' + w(b.m) + ' 万</em>,区间 ' + wr(b.lo, b.hi) + '</div>';
var nodes = [];
if (b.n1 != null) nodes.push(['养到 1 岁累计', b.n1]);
if (b.n2 != null) { nodes.push(['养到 2 岁累计', b.n2]); nodes.push(['养到 3 岁累计', b.n3]); }
if (nodes.length) {
h += '<div class="rc-nodes" style="grid-template-columns:repeat(' + nodes.length + ',1fr)">' + nodes.map(function (x) {
return '<div><span>' + x[0] + '</span><b class="mono">' + w(x[1]) + '万</b></div>';
}).join('') + '</div>';
if ([0, 1, 2].some(function (i) { return st.indexOf(i) < 0; }) || (b.n2 != null && st.indexOf(3) < 0)) h += '<div class="rc-tip">累计只算你选的阶段,没选的不计入</div>';
}
if (inc) {
var pw = Math.min(100, inc.pct);
h += '<div class="rc-inc"><div class="h">按你家月收入 <b class="mono">¥' + money(inc.inc) + '</b> 算</div>' +
'<div class="big">这张账单 ≈ 你家 <em class="mono">' + inc.txt + '</em>的收入</div>' +
'<div class="pbar"><i class="' + (inc.pct > 50 ? 'hot' : '') + '" style="width:' + pw.toFixed(1) + '%"></i></div>' +
'<div class="pl"><span>所选阶段平均每月花 <b class="mono">¥' + money(r100(inc.avg)) + '</b></span><span class="' + (inc.pct > 50 ? 'w' : '') + '">占收入 <b class="mono">' + Math.round(inc.pct) + '%</b></span></div>' +
'<div class="rc-tip">' + (inc.pct <= 20 ? '压力不大,按自己的节奏来。' : inc.pct <= 35 ? '要花心思规划,但扛得住。' : inc.pct <= 50 ? '会比较吃紧,提前存一笔会从容很多。' : '超出不少,可以换条路线看看,或把大项往后挪一挪。') + '</div></div>';
}
h += '<div class="rc-x">' +
(inc ? '' : '<div class="li"><span class="n">' + (isAll ? '平均每月' : '所选阶段平均每月') + '</span><span class="d"></span><span class="v mono">' + money(r100(b.m / (b.months || 1))) + '</span></div>') +
(has(st, [8]) || isAll ? '<div class="li"><span class="n">如果还上大学(' + b.col.n + ')</span><span class="d"></span><span class="v mono">+' + wr(b.col.v[0], b.col.v[1]) + '</span></div>' +
'<div class="li" style="color:#B5BAC2"><span class="n">(大学不计入合计)</span><span class="d" style="border:0"></span><span class="v"></span></div>' : '') +
'<div class="li warm"><span class="n">时间、心思和拥抱</span><span class="d"></span><span class="v">无法计价</span></div></div>' +
'<div class="hr"></div>' +
'<div class="rc-f"><b>谢谢惠顾</b>宝宝一经签收,不退不换<br>账单按行情中间值估算,仅供参考' + barcode(+billNo.slice(-5) || 7) + '<div class="rc-no mono">' + billNo + '</div></div>';
$('#paper').innerHTML = h;
renderTools();
[].forEach.call(document.querySelectorAll('#sRes .fu'), function (el) { el.classList.remove('in'); });
show('sRes');
printIn(animate);
}
/* 打印动画:纸从打印口往下吐出来(容器高度 0→H,同时纸 translateY(-H)→0) */
var printT;
function printIn(animate) {
var feed = $('#feed'), paper = $('#paper'), pr = $('#printer');
clearTimeout(printT);
feed.style.transition = paper.style.transition = 'none';
feed.style.height = ''; paper.style.transform = '';
if (!animate || reduce) { pr.classList.remove('busy'); observe(); return; }
var H = feed.offsetHeight, P = paper.offsetHeight;
var dur = Math.min(4500, 1400 + P * 0.5);
feed.style.height = '0px'; paper.style.transform = 'translateY(' + (-P) + 'px)';
void feed.offsetHeight;
var ease = 'cubic-bezier(.4,.05,.3,1)';
feed.style.transition = 'height ' + dur + 'ms ' + ease;
paper.style.transition = 'transform ' + dur + 'ms ' + ease;
feed.style.height = H + 'px'; paper.style.transform = 'translateY(0)';
pr.classList.add('busy');
printT = setTimeout(function () {
feed.style.transition = paper.style.transition = 'none';
feed.style.height = ''; paper.style.transform = '';
pr.classList.remove('busy');
observe();
}, dur + 60);
}
var tt;
function toast(msg) {
var el = $('#toast'); el.textContent = msg; el.classList.add('on');
clearTimeout(tt); tt = setTimeout(function () { el.classList.remove('on'); }, 2400);
}
/* ================= canvas 小工具 ================= */
var FONT = '-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif';
var MONO = '"SF Mono",Menlo,Consolas,"Roboto Mono","Courier New",monospace';
function rr(c, x, y, w2, h2, r) {
c.beginPath(); c.moveTo(x + r, y); c.arcTo(x + w2, y, x + w2, y + h2, r); c.arcTo(x + w2, y + h2, x, y + h2, r);
c.arcTo(x, y + h2, x, y, r); c.arcTo(x, y, x + w2, y, r); c.closePath();
}
var isWx = /MicroMessenger/i.test(navigator.userAgent);
/* ================= 保存账单图片:把整张小票原样画成长图(宽 750,高按内容算) =================
iOS 单张 canvas 建议 ≤ 约 16000px 高 / 1600 万像素:先按标准行高量一遍,超了就压行高(最低 32),仍超就整体缩小。 */
var BILL_MAX_H = 15800;
function drawBill() {
var b = bill, R = ROUTES[route], s = b.s, st = sel(), inc = incomeOf(b);
var nodes = [];
if (b.n1 != null) nodes.push(['养到 1 岁累计', b.n1]);
if (b.n2 != null) { nodes.push(['养到 2 岁累计', b.n2]); nodes.push(['养到 3 岁累计', b.n3]); }
var tipNodes = nodes.length && ([0, 1, 2].some(function (i) { return st.indexOf(i) < 0; }) || (b.n2 != null && st.indexOf(3) < 0));
var meta = [['账单号', billNo], ['日期', billAt], ['范围', b.range + ' · ' + b.n + ' 项'], ['收银员', '兜爸']];
var W = 750, PX = 34, PW = W - PX * 2, X0 = PX + 34, X1 = W - PX - 34;
var mc = document.createElement('canvas').getContext('2d');
// 画一遍(dry=true 时只量高度)
function paint(c, dry, rh) {
var y = 0;
function T(text, x, yy, font, color, align) { if (dry) return; c.font = font; c.fillStyle = color; c.textAlign = align || 'left'; c.fillText(text, x, yy); }
function tw(text, font) { mc.font = font; return mc.measureText(text).width; }
function dash(yy, solid, dbl) {
if (dry) return;
c.save(); c.strokeStyle = solid ? '#2B2F36' : '#C9CED4'; c.lineWidth = solid ? 2 : 2.5;
if (!solid) c.setLineDash([9, 7]);
c.beginPath(); c.moveTo(X0, yy); c.lineTo(X1, yy); c.stroke();
if (dbl) { c.beginPath(); c.moveTo(X0, yy + 6); c.lineTo(X1, yy + 6); c.stroke(); }
c.restore();
}
function row(name, val, yy, o) { // 名称左、金额右对齐,中间点线
o = o || {};
var fn = (o.bold ? '800 ' : '400 ') + (o.fs || 26) + 'px ' + FONT, fv = (o.bold ? '800 ' : '500 ') + (o.vs || o.fs || 26) + 'px ' + MONO;
T(name, X0, yy, fn, o.c || '#3B4048');
T(val, X1, yy, fv, o.vc || o.c || '#2B2F36', 'right');
if (!dry && o.dots !== false) {
var a = X0 + tw(name, fn) + 12, z = X1 - tw(val, fv) - 12;
if (z > a) { c.save(); c.fillStyle = '#C3C8CE'; for (var x = a; x < z; x += 7) c.fillRect(x, yy - 8, 2.5, 2.5); c.restore(); }
}
}
// 纸外顶部
y = 40;
var paperTop = y;
if (!dry) { // 纸先画出来(高度在 dry 阶段已量好)
var ph = paint.ph, n = Math.round(PW / 22), tw2 = PW / n;
c.save(); c.shadowColor = 'rgba(30,60,45,.16)'; c.shadowBlur = 28; c.shadowOffsetY = 8; c.fillStyle = '#fff';
c.beginPath(); c.moveTo(PX, paperTop);
for (var i = 0; i < n; i++) { c.lineTo(PX + tw2 * i + tw2 / 2, paperTop - 11); c.lineTo(PX + tw2 * (i + 1), paperTop); }
c.lineTo(PX + PW, paperTop + ph);
for (var j = n; j > 0; j--) { c.lineTo(PX + tw2 * j - tw2 / 2, paperTop + ph + 11); c.lineTo(PX + tw2 * (j - 1), paperTop + ph); }
c.closePath(); c.fill(); c.restore();
}
// 表头
y += 74;
if (!dry) {
var lg = '兜爸心选 · 养娃账单', lf = '800 36px ' + FONT, lw = tw(lg, lf);
T(lg, W / 2 + 20, y, lf, '#1F2329', 'center');
c.save(); c.strokeStyle = '#18A058'; c.lineWidth = 3.4; c.lineCap = 'round'; // 小叶子
var lx = W / 2 + 20 - lw / 2 - 30, ly = y - 13;
c.beginPath(); c.moveTo(lx - 12, ly + 12); c.quadraticCurveTo(lx - 12, ly - 12, lx + 14, ly - 14); c.quadraticCurveTo(lx + 12, ly + 12, lx - 12, ly + 12); c.stroke();
c.beginPath(); c.moveTo(lx - 12, ly + 12); c.lineTo(lx + 2, ly - 2); c.stroke(); c.restore();
}
y += 42;
T((b.isAll ? '从怀孕一直算到 18 岁' : b.range).split('').join(' '), W / 2, y, '400 22px ' + FONT, '#86909C', 'center');
y += 30;
meta.forEach(function (m) { y += 40; row(m[0], m[1], y, { fs: 24, c: '#4E5969', vc: '#4E5969', dots: false }); });
y += 26; dash(y, true);
y += 40; row('项目', '金额(元)', y, { fs: 22, c: '#86909C', vc: '#86909C', dots: false });
// 各段
b.segs.forEach(function (sg) {
y += 22; dash(y);
y += 54;
if (!dry) {
var no = '0' + (sg.i + 1), nf = '700 20px ' + MONO, nw = tw(no, nf) + 16;
c.fillStyle = '#18A058'; rr(c, X0, y - 26, nw, 32, 7); c.fill();
T(no, X0 + nw / 2, y - 3, nf, '#fff', 'center');
var tf = '800 28px ' + FONT;
T(sg.t, X0 + nw + 12, y, tf, '#1F2329');
T(sg.a + ' · ' + sg.it.length + ' 项', X0 + nw + 12 + tw(sg.t, tf) + 14, y, '400 21px ' + FONT, '#86909C');
}
y += 10;
sg.it.forEach(function (x) {
y += rh;
var neg = x.m < 0;
row(x.n, money(x.m), y, { c: neg ? '#18A058' : '#3B4048', vc: neg ? '#18A058' : '#2B2F36' });
});
y += 20;
if (!dry) { c.fillStyle = '#EEF0F3'; c.fillRect(X0, y, X1 - X0, 2); }
y += 42; row('小计', money(sg.m), y, { bold: true, fs: 26, vs: 28, c: '#1F2329', dots: false });
});
// 合计
y += 30; dash(y, true, true);
y += 82;
T('合 计', X0, y - 6, '800 30px ' + FONT, '#1F2329');
T('¥' + money(b.m), X1, y, '800 60px ' + MONO, '#1F2329', 'right');
y += 42;
if (!dry) {
var rg = ',区间 ' + wr(b.lo, b.hi), rf = '400 24px ' + FONT, ef = '800 24px ' + FONT, em = w(b.m) + ' 万';
T(rg, X1, y, rf, '#4E5969', 'right');
var x2 = X1 - tw(rg, rf);
T(em, x2, y, ef, '#18A058', 'right'); x2 -= tw(em, ef) + 4;
T('约', x2, y, rf, '#4E5969', 'right');
}
if (nodes.length) {
y += 28;
var bh = 104, cw = (X1 - X0) / nodes.length;
if (!dry) {
c.save(); c.strokeStyle = '#C9CED4'; c.lineWidth = 2.5; c.setLineDash([9, 7]);
rr(c, X0, y, X1 - X0, bh, 18); c.stroke();
for (var k = 1; k < nodes.length; k++) { c.beginPath(); c.moveTo(X0 + cw * k, y); c.lineTo(X0 + cw * k, y + bh); c.stroke(); }
c.restore();
nodes.forEach(function (nd, k2) {
var cx = X0 + cw * k2 + cw / 2;
T(nd[0], cx, y + 38, '400 21px ' + FONT, '#86909C', 'center');
T(w(nd[1]) + '万', cx, y + 80, '800 30px ' + MONO, '#1F2329', 'center');
});
}
y += bh;
if (tipNodes) { y += 34; T('累计只算你选的阶段,没选的不计入', X0, y, '400 21px ' + FONT, '#86909C'); }
}
if (inc) {
y += 28;
var ih = 236, iy = y, pw2 = Math.min(100, inc.pct), hot = inc.pct > 50;
if (!dry) {
c.save(); c.fillStyle = '#F6FAF8'; rr(c, X0, iy, X1 - X0, ih, 18); c.fill();
c.strokeStyle = '#BFE3CE'; c.lineWidth = 2.5; c.setLineDash([9, 7]); c.stroke(); c.restore();
var ix = X0 + 24, ix1 = X1 - 24;
var h1 = '按你家月收入 ', h1f = '400 22px ' + FONT;
T(h1, ix, iy + 44, h1f, '#86909C');
var hx = ix + tw(h1, h1f);
T('¥' + money(inc.inc), hx, iy + 44, '800 22px ' + MONO, '#1F2329');
T(' 算', hx + tw('¥' + money(inc.inc), '800 22px ' + MONO), iy + 44, h1f, '#86909C');
var a1 = '这张账单 ≈ 你家 ', af = '800 28px ' + FONT, bf = '800 38px ' + FONT;
T(a1, ix, iy + 94, af, '#1F2329');
var ax = ix + tw(a1, af);
T(inc.txt, ax, iy + 94, bf, '#18A058');
T('的收入', ax + tw(inc.txt, bf) + 4, iy + 94, af, '#1F2329');
c.fillStyle = '#E3EEE8'; rr(c, ix, iy + 116, ix1 - ix, 18, 9); c.fill();
var gr = c.createLinearGradient(ix, 0, ix1, 0);
if (hot) { gr.addColorStop(0, '#F2B57A'); gr.addColorStop(1, '#D9823B'); } else { gr.addColorStop(0, '#79CC9E'); gr.addColorStop(1, '#18A058'); }
c.fillStyle = gr; rr(c, ix, iy + 116, Math.max(18, (ix1 - ix) * pw2 / 100), 18, 9); c.fill();
T('所选阶段平均每月花 ¥' + money(r100(inc.avg)), ix, iy + 170, '400 23px ' + FONT, '#4E5969');
T('占收入 ' + Math.round(inc.pct) + '%', ix1, iy + 170, '800 23px ' + FONT, hot ? '#B4733A' : '#1F2329', 'right');
T(inc.pct <= 20 ? '压力不大,按自己的节奏来。' : inc.pct <= 35 ? '要花心思规划,但扛得住。' : inc.pct <= 50 ? '会比较吃紧,提前存一笔会从容很多。' : '超出不少,可以换条路线看看,或把大项往后挪一挪。',
ix, iy + 210, '400 21px ' + FONT, '#86909C');
}
y += ih;
}
y += 24;
if (!inc) { y += 34; row(b.isAll ? '平均每月' : '所选阶段平均每月', money(r100(b.m / (b.months || 1))), y, { fs: 23, c: '#4E5969', vc: '#4E5969' }); }
if (has(st, [8])) {
y += 40; row('如果还上大学(' + b.col.n + ')', '+' + wr(b.col.v[0], b.col.v[1]), y, { fs: 23, c: '#4E5969', vc: '#4E5969' });
y += 34; T('(大学不计入合计)', X0, y, '400 21px ' + FONT, '#B5BAC2');
}
y += 42; row('时间、心思和拥抱', '无法计价', y, { fs: 24, bold: true, c: '#B4733A', vc: '#B4733A' });
y += 28; dash(y);
y += 56; T('谢 谢 惠 顾', W / 2, y, '800 28px ' + FONT, '#1F2329', 'center');
y += 38; T('宝宝一经签收,不退不换', W / 2, y, '400 22px ' + FONT, '#86909C', 'center');
y += 32; T('账单按行情中间值估算,仅供参考', W / 2, y, '400 22px ' + FONT, '#86909C', 'center');
y += 26;
if (!dry) { // 条码(与页面同一个种子)
var bx = W / 2 - 230, sd = +billNo.slice(-5) || 7, xx = 0, k3 = 460 / 236;
c.fillStyle = '#2B2F36';
while (xx < 236) { sd = (sd * 9301 + 49297) % 233280; var bw = 1 + (sd % 3), gp = 1 + ((sd >> 3) % 3); c.fillRect(bx + xx * k3, y, bw * k3, 78); xx += bw + gp; }
}
y += 78 + 32;
T(billNo.split('').join(' '), W / 2, y, '500 20px ' + MONO, '#86909C', 'center');
y += 34;
paint.ph = y - paperTop;
// 纸外落款
y += 11 + 56;
T('兜爸心选', W / 2, y, '800 28px ' + FONT, '#18A058', 'center');
y += 36;
T('孕期专题 · 生娃计算器 · 估算仅供参考', W / 2, y, '400 21px ' + FONT, '#86909C', 'center');
y += 44;
return y;
}
var nItems = b.n, rh = 46, H = paint(null, true, rh);
if (H > BILL_MAX_H) { rh = Math.max(32, rh - Math.ceil((H - BILL_MAX_H) / nItems)); H = paint(null, true, rh); }
var k = Math.min(1, BILL_MAX_H / H, Math.sqrt(16000000 / (W * H))); // 仍超就整体缩小
var cv = document.createElement('canvas');
cv.width = Math.round(W * k); cv.height = Math.round(H * k);
var c = cv.getContext('2d');
c.scale(k, k);
var g = c.createLinearGradient(0, 0, 0, H);
g.addColorStop(0, '#EAF7F0'); g.addColorStop(.5, '#F5FBF8'); g.addColorStop(1, '#EAF7F0');
c.fillStyle = g; c.fillRect(0, 0, W, H);
c.textBaseline = 'alphabetic';
paint(c, false, rh);
return cv.toDataURL('image/png');
}
function openMask(url, alt, name, tip, long) {
var im = $('#maskImg'); im.src = url; im.alt = alt;
$('#maskTip').textContent = tip;
var dl = $('#dlBtn'); dl.href = url; dl.download = name; dl.hidden = isWx;
var m = $('#mask'); m.classList.toggle('long', !!long); m.classList.add('on');
$('#mwrap').scrollTop = 0;
}
$('#billBtn').addEventListener('click', function () {
if (!bill) return;
toast('正在生成账单图片…');
setTimeout(function () {
try {
var url = drawBill();
$('#toast').classList.remove('on');
openMask(url, '我的养娃账单', '养娃账单.png', '长按图片,保存到相册', true);
} catch (e) { toast('生成失败,截个图保存也可以'); }
}, 30);
});
$('#maskClose').addEventListener('click', function () { $('#mask').classList.remove('on'); });
$('#mask').addEventListener('click', function (e) { if (e.target === this) this.classList.remove('on'); });
/* ================= 推荐工具 ================= */
function renderTools() {
if (!window.TopicTools) return;
var why = [
['列清单按需买,能借能复用的先划掉', '跟着产检表走,不额外加做没必要的项目', '补剂按需补,不乱花冤枉钱'],
['月嫂、待产包要准备的,一张清单理清', '产检时间和自费项目提前排好', '按需补,不跟风买补剂'],
['好东西也要买在点上,列清单不重复买', '私立、特需的自费项先排进日历,心里有数', '补剂按需补,贵的不一定是对的']
][route];
var el = $('#tools');
el.className = 'fu' + (el.classList.contains('in') ? ' in' : '');
TopicTools.render(el, [
{ id: 'daichanqingdan', why: why[0], tag: route === 0 ? '省钱' : '' },
{ id: 'chanjianrili', why: why[1] },
{ id: 'bushui', why: why[2] }
], { title: route === 2 ? '钱花在刀刃上' : '帮你少花冤枉钱', color: '#18A058', bg: '#EAF7F0', wk: 0 });
}
var io = 'IntersectionObserver' in window ? new IntersectionObserver(function (es) {
es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
}, { root: sc, threshold: .06 }) : null;
function observe() {
[].forEach.call(document.querySelectorAll('#sRes .fu'), function (el, i) {
el.style.transitionDelay = (i % 3) * 80 + 'ms';
if (io) io.observe(el); else el.classList.add('in');
});
}
/* 重新测试 = 这份账单作废:清掉「看上次的账单」,存成停在开始页 */
$('#reTest').addEventListener('click', function () { route = -1; A = {}; cur = 0; hasBill = false; billNo = ''; billAt = ''; renderStart(); show('sStart'); });
/* ================= 存取(v5;兼容读取 v4:路线 + 6 题答案,阶段按全部、收入为空) ================= */
function save() {
try {
localStorage.setItem(KEY, JSON.stringify(route < 0 ? { scr: 'sStart', done: false } /* 没选路线(重新测试后):只记停在开始页 */
: { route: route, rid: ROUTES[route].id, a: A, done: hasBill, no: billNo, at: billAt, scr: scr, cur: cur }));
} catch (e) {}
}
var lastScr = '', lastCur = 0;
function load() {
try {
var o = JSON.parse(localStorage.getItem(KEY) || 'null') || JSON.parse(localStorage.getItem(KEY4) || 'null');
if (o) { lastScr = o.scr || ''; lastCur = +o.cur || 0; }
if (o && !o.rid && o.route >= 0) o.rid = ['rich', 'mid', 'low'][o.route]; /* 旧记录是旧顺序(富豪在前),按 id 换算 */
var ri = o ? ROUTES.map(function (x) { return x.id; }).indexOf(o.rid) : -1;
if (ri < 0) return false;
route = ri;
var a = o.a || {};
QS.forEach(function (q) { var v = a[q.k]; if (q.o && typeof v === 'number' && v >= 0 && v < q.o.length) A[q.k] = v; });
if (Array.isArray(a.st)) { var st = a.st.filter(function (i) { return ALL.indexOf(i) >= 0; }); if (st.length) A.st = st.sort(function (x, y) { return x - y; }); }
if (typeof a.inc === 'number' && a.inc >= 0) A.inc = a.inc;
billNo = o.no || ''; billAt = o.at || '';
if (!A.st) A.st = ALL.slice();
var need = flow().filter(function (q) { return q.o; });
var all = need.every(function (q) { return val(q.k) != null; }); // 新加的题没答过就按默认(旧账单照样能看)
if (all && !billNo) newBill();
return !!o.done && all;
} catch (e) { return false; }
}
hasBill = load();
renderStart();
/* 回到上次停的那一屏;旧记录没有 scr:有账单就看账单(原行为) */
if (lastScr === 'sQ' && route >= 0) { cur = Math.max(0, Math.min(lastCur, flow().length - 1)); renderQ(); }
else if (lastScr === 'sStart' || !hasBill) show('sStart');
else renderRes(false);
booting = false;
/* 返回:从首页点进来就 back;直接打开(分享 / 刷新)就回同目录的首页,身份原样带回 */
$('#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 > 4); }, { passive: true });
/* 禁止下拉回弹:固定外壳 + 容器滚动 + 到顶/到底时拦 touchmove */
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.target.closest && e.target.closest('.mask.long .mwrap'))) e.preventDefault(); }, { passive: false });
})();
</script>
<script src="../_shared/brand.js?v=26110203" data-after="#sStart .tiny, #sRes .note"></script>
<script src="../../js/last-sub.js?v=26110905"></script> <!-- 11-07 小程序刷新回到这一页(见 js/last-sub.js) -->
<script src="../../js/pv.js?v=26110653"></script> <!-- 11-07 页面访问记录(推送效果看板,见 js/pv.js) -->
</body>
</html>