1441 lines
111 KiB
HTML
1441 lines
111 KiB
HTML
<!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=26110907"></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 { '&': '&', '<': '<', '>': '>', '"': '"' }[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>
|