1409 lines
100 KiB
HTML
1409 lines
100 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;
|
||
⑥ v6(11-01):账单下加「保存账单图片」,canvas 把整张小票画成长图(宽 750,高按内容,超 15800px 先压行高再整体缩小),复用卡片弹层、长图可滚动。改价格只改脚本里的 COST 表。
|
||
存储 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.two{flex-direction:row}
|
||
.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}
|
||
/* 第 1 题:两张大卡 */
|
||
.opts.two .opt{flex:1;min-width:0;flex-direction:column;align-items:center;justify-content:center;gap:8px;min-height:188px;padding:18px 10px;text-align:center}
|
||
.opts.two .opt .oi{width:76px;height:76px;border-radius:50%}
|
||
.opts.two .opt .oi .ic{width:40px;height:40px;stroke-width:1.5}
|
||
.opts.two .opt b{font-size:18px}
|
||
.opts.two .opt .rd{position:absolute;opacity:0}
|
||
.opts.two .opt{position:relative}
|
||
.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}
|
||
|
||
/* ============ 情感收尾 ============ */
|
||
.mom{position:relative;margin-top:34px;padding:24px 20px 20px;border-radius:26px;background:linear-gradient(170deg,var(--warm) 0%,#FFFBF6 60%,#fff 100%);border:1px solid #F6E7D6;overflow:hidden}
|
||
.mom .art{position:absolute;right:14px;top:16px;width:74px;height:74px;opacity:.9}
|
||
.mom .k{font-size:12px;font-weight:700;letter-spacing:.18em;color:var(--wt)}
|
||
.mom h3{margin-top:8px;font-size:25px;font-weight:800;line-height:1.35;color:var(--h)}
|
||
.mom p{margin-top:10px;font-size:15px;line-height:1.85;color:#5B5249}
|
||
.mom p b{color:var(--h)}
|
||
.mom p em{white-space:nowrap;font-style:normal;font-weight:800;color:var(--wt)}
|
||
.mom .say{margin-top:14px;font-size:17px;font-weight:700;line-height:1.7;color:var(--h)}
|
||
.mom .say q{quotes:none;display:inline;background:linear-gradient(transparent 60%,rgba(244,196,140,.45) 0)}
|
||
.msg{position:relative;margin-top:16px;padding:14px 14px 12px;border-radius:18px;background:#fff;border:1px solid #F3E3D0}
|
||
.msg .q{font-size:14.5px;line-height:1.75;color:var(--h);min-height:51px}
|
||
.msg .ft{display:flex;align-items:center;justify-content:space-between;margin-top:8px}
|
||
.msg .dt{display:flex;gap:5px}
|
||
.msg .dt i{width:6px;height:6px;border-radius:50%;background:#EAD8C3;transition:all .2s}
|
||
.msg .dt i.on{width:16px;border-radius:9px;background:var(--wt)}
|
||
.msg .sw{display:inline-flex;align-items:center;gap:4px;height:30px;padding:0 10px;border-radius:99px;background:var(--warm);font-size:13px;font-weight:600;color:var(--wt)}
|
||
.msg .sw .ic{width:15px;height:15px}
|
||
.macts{display:flex;flex-direction:column;gap:10px;margin-top:14px}
|
||
.macts .btn.w{background:#fff;color:var(--c);border:1px solid var(--c);box-shadow:none}
|
||
|
||
.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>
|
||
</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-copy" viewBox="0 0 24 24"><rect x="8.5" y="8.5" width="11.5" height="11.5" rx="2.5"/><path d="M15.5 8.5V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7.5a2 2 0 0 0 2 2h2.5"/></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-girl" viewBox="0 0 24 24"><circle cx="12" cy="8.5" r="4"/><path d="M8 8.5c0-3.5 1.5-5.5 4-5.5s4 2 4 5.5v3.8M8 8.5v3.8"/><path d="M5.5 21c.7-3.6 3.2-5.8 6.5-5.8s5.8 2.2 6.5 5.8"/></symbol>
|
||
<symbol id="o-boy" viewBox="0 0 24 24"><circle cx="12" cy="9" r="4"/><path d="M8.2 7.8c1.2.2 3.4-.2 4.8-1.8.8 1.2 2 1.8 2.8 1.8"/><path d="M5.5 21c.7-3.6 3.2-5.8 6.5-5.8s5.8 2.2 6.5 5.8"/></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-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>
|
||
<div class="racts">
|
||
<button class="btn ghost press" id="reRoute"><svg class="ic"><use href="#i-refresh" xlink:href="#i-refresh"/></svg>换条路线再算</button>
|
||
<button class="btn ghost press" id="reAns"><svg class="ic"><use href="#i-edit" xlink:href="#i-edit"/></svg>改一下答案</button>
|
||
</div>
|
||
|
||
<div class="mom fu" id="mom">
|
||
<svg class="art" viewBox="0 0 80 80" aria-hidden="true"><circle cx="40" cy="40" r="38" fill="#FFEBD6"/><path d="M40 60c-9-6.3-16-12.4-16-20.2 0-5 3.7-8.8 8.3-8.8 3.3 0 6 1.8 7.7 4.6 1.7-2.8 4.4-4.6 7.7-4.6 4.6 0 8.3 3.8 8.3 8.8C56 47.6 49 53.7 40 60z" fill="#F7B98A" stroke="#C9824A" stroke-width="2" stroke-linejoin="round"/><path d="M40 60v8M36 66c1.5-1.8 2.8-2.5 4-2.5s2.5.7 4 2.5" fill="none" stroke="#18A058" stroke-width="2" stroke-linecap="round"/><path d="M33.5 38.5c.6-1.8 2-3 3.8-3.2" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
|
||
<div class="k">写在账单后面</div>
|
||
<h3 id="momH"></h3>
|
||
<p id="momP"></p>
|
||
<p class="say">如果可以的话,今天对她说一句:<br><q>妈,辛苦了。</q></p>
|
||
<div class="msg">
|
||
<div class="q" id="msgQ"></div>
|
||
<div class="ft"><span class="dt" id="msgDt"></span><button class="sw press" id="msgSw"><svg class="ic"><use href="#i-refresh" xlink:href="#i-refresh"/></svg>换一句</button></div>
|
||
</div>
|
||
<div class="macts">
|
||
<button class="btn press" id="copyBtn"><svg class="ic"><use href="#i-copy" xlink:href="#i-copy"/></svg>复制这句话,发给妈妈</button>
|
||
<button class="btn w press" id="cardBtn"><svg class="ic"><use href="#i-img" xlink:href="#i-img"/></svg>生成卡片</button>
|
||
</div>
|
||
</div>
|
||
|
||
<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: 'rich', badge: 'b-rich', who: '我是大富豪', how: '富养娃', line: '钱能解决的,都不让娃将就',
|
||
tags: ['私立高端产检', '月子中心', '育儿嫂', '国际学校'], def: { care: 2, school: 2, extra: 2 } },
|
||
{ id: 'mid', badge: 'b-mid', who: '我是中产家', how: '从容养', line: '不卷也不亏待,日子过得舒展',
|
||
tags: ['公立特需', '月嫂', '托班', '适度早教'], def: { care: 1, school: 0, extra: 1 } },
|
||
{ id: 'low', badge: 'b-low', who: '我是普通人', how: '量力养', line: '能省则省,爱一点不少',
|
||
tags: ['公立医院', '家人坐月子', '老人帮带', '能省则省'], def: { care: 0, school: 0, extra: 0 } }
|
||
];
|
||
function tierOf(r) { return [2, 1, 0][r]; } // 档次 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: 'sex', h: '先认识一下,你是…', s: '账单最后,有句话想对你说', two: 1, o: [
|
||
['女生', '我是准妈妈', 'girl'], ['男生', '我是准爸爸', 'boy']] },
|
||
{ 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: 'care', h: '0~3 岁,白天谁带娃?', s: '产假结束后的安排,差别很大', if: function (st) { return has(st, [3, 4, 5]); }, o: [
|
||
['老人或自己带', '给老人一点补贴,3 岁上幼儿园', 'family'], ['家里带,2 岁上托班', '第 3 年交托班费', 'flag'],
|
||
['请育儿嫂', '住家或白班阿姨,带到上幼儿园', 'nurse'], ['1 岁半上托育园', '全日托,按月费算', 'school']] },
|
||
{ k: 'school', h: '上学打算走哪条路?', s: '幼儿园到高中,学费差距最大的一项', if: function (st) { return has(st, [5, 6, 7, 8]); }, o: [
|
||
['公立为主', '普惠园 + 就近公立学校', 'book'], ['民办私立', '民办园 + 民办中小学', 'cap'], ['国际学校', '双语园 + 国际学校', 'globe']] },
|
||
{ k: 'extra', h: '早教和课外班呢?', s: '早教、兴趣班、补习,一路算下去', if: function (st) { return has(st, [3, 4, 5, 6, 7, 8]); }, o: [
|
||
['顺其自然', '以玩为主,偶尔报一门', 'sprout'], ['适度报几门', '一两门兴趣 + 必要补习', 'music'], ['拼一把', '早教、兴趣、补习一个不落', 'rocket']] },
|
||
{ k: 'inc', type: 'income', h: '你家每月收入大概多少?', s: '填了能看看这张账单相当于你家几年的收入' }
|
||
];
|
||
var QK = {}; QS.forEach(function (q) { QK[q.k] = q; });
|
||
var CITY_S = ['一线城市', '新一线/省会', '地级市', '县城乡镇'];
|
||
|
||
/* ================= 费用表(单位:元,2025~2026 国内常见公开行情;基准 = 新一线及省会) =================
|
||
I(名称, [量力低,高], [从容低,高], [富养低,高], 城市系数, 二胎复用系数)。名称可以是 [量力名, 从容名, 富养名];某档 [0,0] 表示这档不花这笔。
|
||
D(答案键, 名称或按答案的名称数组, [按答案的 [低,高]...], 城市系数):随答案变(上学路线 school / 课外班 extra)。
|
||
城市系数: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 D(g, n, vs, k) { return { g: g, n: n, dv: vs, k: k }; }
|
||
var SUB = [3600, 9600], NANNY = [60000, 90000], TUOBAN = [20000, 45000], DAYCARE = [27500, 55000];
|
||
var COST = [
|
||
// ① 孕期产检
|
||
[
|
||
I(['建档和常规产检(约 12 次)', '特需门诊产检(约 12 次)', '私立医院产检套餐'], [1500, 3000], [5000, 10000], [15000, 30000], 'm'),
|
||
I('B 超(NT、大排畸、晚孕)', [800, 1500], [1500, 3000], [2000, 4000], 'm'),
|
||
I(['唐氏筛查', '无创 DNA(NIPT)', '无创 DNA 加强版'], [200, 400], [1500, 2500], [2500, 4000], 'm'),
|
||
I('四维彩超', [300, 600], [500, 1000], [800, 2000], 'm'),
|
||
I('糖耐量和血液检查', [300, 600], [500, 1000], [800, 1500], 'm'),
|
||
I('胎心监护', [200, 500], [400, 800], [800, 1500], 'm'),
|
||
I('孕妇奶粉', [0, 600], [1200, 2400], [2400, 4800], 'g'),
|
||
I('叶酸、DHA、钙铁维生素', [500, 1200], [1500, 3000], [3000, 6000], 'g'),
|
||
I('孕妇装和孕妇内衣', [600, 1500], [1500, 3500], [4000, 10000], 'g'),
|
||
I('防辐射服、托腹带', [150, 400], [400, 800], [800, 2000], 'g'),
|
||
I('妊娠油和孕期护肤', [100, 300], [400, 1000], [1500, 4000], 'g'),
|
||
I('孕妇枕、孕妇鞋', [150, 400], [300, 800], [800, 2000], 'g'),
|
||
I(['孕妇学校(免费为主)', '孕期瑜伽和课程', '孕期私教和产前课程'], [0, 200], [1000, 3000], [5000, 12000], 's'),
|
||
I('产检交通和打车', [500, 1500], [1500, 3000], [3000, 6000], 's'),
|
||
I(['孕妇照(自己拍为主)', '孕妇写真', '孕妇写真(棚拍)'], [0, 300], [1000, 3000], [3000, 8000], 's'),
|
||
I('待产包', [500, 1200], [1200, 2500], [2500, 5000], 'g', 0.6)
|
||
],
|
||
// ② 分娩住院
|
||
[
|
||
I(['公立普通病房分娩', '公立特需分娩', '私立医院分娩套餐'], [3000, 8000], [8000, 18000], [35000, 80000], 'm'),
|
||
I(['无痛分娩', '无痛分娩和导乐', '无痛和一对一助产'], [1000, 2500], [2000, 4000], [3000, 8000], 'm'),
|
||
I(['病房床位', '单间升级', 'VIP 套房升级'], [300, 800], [2000, 5000], [5000, 15000], 'm'),
|
||
I('陪护床和护工', [300, 800], [1000, 2500], [2000, 5000], 's'),
|
||
I('新生儿筛查(听力、足跟血)', [300, 800], [500, 1200], [1500, 3000], 'm'),
|
||
I('新生儿尿片、衣服、包被', [200, 500], [400, 800], [800, 1500], 'g'),
|
||
I('产妇用品(产褥垫、收腹带)', [200, 500], [400, 800], [800, 1500], 'g'),
|
||
I(['住院餐', '住院营养餐', '院内营养月子餐'], [300, 800], [800, 1500], [2000, 4000], 's'),
|
||
I('脐带血存储', [0, 0], [0, 0], [15000, 25000], '0'),
|
||
I('报喜红包和喜糖', [500, 1500], [1000, 3000], [3000, 8000], 'g'),
|
||
I('家属交通和餐费', [300, 800], [500, 1500], [1000, 3000], 's'),
|
||
I('生育保险报销(预估)', [-5000, -2500], [-4500, -2000], [-2500, -500], 'm')
|
||
],
|
||
// ③ 坐月子
|
||
[
|
||
I(['月子餐食材(家人做)', '月嫂 26 天', '高端月子中心 28 天'], [2500, 5000], [12000, 20000], [50000, 120000], 's'),
|
||
I('月子餐食材和营养汤', [0, 0], [2000, 4000], [0, 0], 'g'),
|
||
I('催乳和通乳', [0, 500], [800, 2000], [2000, 5000], 's'),
|
||
I('吸奶器和储奶袋', [300, 800], [800, 2000], [2000, 4000], 'g', 0.5),
|
||
I('哺乳内衣和防溢乳垫', [200, 500], [400, 900], [800, 2000], 'g'),
|
||
I('产妇卫生巾和月子服', [300, 700], [500, 1200], [1000, 2500], 'g'),
|
||
I(['产后盆底康复(医院)', '产后修复疗程', '产后修复和私教塑形'], [500, 1500], [3000, 6000], [10000, 25000], 's'),
|
||
I('产后 42 天复查', [200, 500], [400, 1000], [1000, 3000], 'm'),
|
||
I('宝宝 42 天体检', [200, 500], [300, 800], [800, 2000], 'm'),
|
||
I('新生儿抚触和游泳', [0, 300], [600, 1500], [2000, 5000], 's'),
|
||
I('新生儿摄影', [0, 500], [1000, 3000], [3000, 8000], 's'),
|
||
I(['满月酒和喜糖', '满月酒和喜糖', '满月宴和回礼'], [2000, 6000], [5000, 15000], [15000, 40000], 'g')
|
||
],
|
||
// ④ 0~1 岁
|
||
[
|
||
I(['国产奶粉(混合喂养)', '国产高端奶粉', '进口有机奶粉'], [3000, 6000], [6000, 10000], [12000, 20000], 'g'),
|
||
I('米粉和辅食', [800, 1500], [1500, 3000], [3000, 6000], 'g'),
|
||
I('奶瓶和奶嘴', [200, 400], [400, 800], [800, 1500], 'g'),
|
||
I('温奶器、消毒器', [200, 400], [400, 900], [1000, 2500], 'g', 0.5),
|
||
I('辅食机、加湿器等小家电', [300, 800], [800, 2000], [2000, 5000], 'g', 0.6),
|
||
I('纸尿裤', [2000, 3200], [3200, 4800], [4800, 7200], 'g'),
|
||
I('湿巾和棉柔巾', [400, 700], [600, 1000], [1000, 1800], 'g'),
|
||
I('隔尿垫和口水巾', [100, 250], [200, 400], [400, 800], 'g'),
|
||
I('洗护用品和护臀膏', [300, 600], [600, 1200], [1500, 3000], 'g'),
|
||
I('婴儿床和床品', [600, 1500], [1500, 3500], [5000, 12000], 'g', 0.4),
|
||
I('推车', [300, 800], [1500, 3500], [4000, 9000], 'g', 0.4),
|
||
I('安全座椅', [400, 900], [1200, 2500], [3000, 6000], 'g', 0.5),
|
||
I('背带和腰凳', [100, 300], [300, 800], [1000, 2500], 'g', 0.5),
|
||
I('衣服鞋袜', [1000, 2000], [2000, 4000], [5000, 10000], 'g', 0.7),
|
||
I('包被和睡袋', [150, 400], [400, 800], [1000, 2500], 'g', 0.6),
|
||
I('玩具和绘本', [400, 1000], [1000, 2500], [3000, 8000], 'g', 0.7),
|
||
D('extra', ['亲子游戏(在家)', '亲子早教体验课', '亲子早教年卡'], [[0, 500], [2000, 5000], [10000, 20000]], 's'),
|
||
I(['自费疫苗(少量)', '自费疫苗(13 价、五联等)', '自费疫苗全打'], [500, 2000], [4000, 6000], [6000, 9000], '0'),
|
||
I(['儿保体检', '儿保体检', '私立儿科体检'], [200, 500], [500, 1200], [2000, 5000], 'm'),
|
||
I(['看病和医药', '看病和医药', '私立儿科看病'], [600, 2000], [1500, 3000], [4000, 10000], 'm'),
|
||
I(['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [400, 600], [800, 2000], [8000, 15000], '0'),
|
||
I('百天照、周岁照', [0, 600], [1000, 3000], [3000, 8000], 's'),
|
||
I('百天宴、周岁宴', [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 岁累计)
|
||
[
|
||
I(['国产奶粉', '国产高端奶粉', '进口高端奶粉'], [3000, 6000], [6000, 10000], [12000, 18000], 'g'),
|
||
I('纸尿裤和拉拉裤', [2000, 3500], [3000, 5000], [4500, 7000], 'g'),
|
||
I('湿巾和洗护用品', [600, 1200], [1000, 2000], [2000, 4000], 'g'),
|
||
I('辅食、水果和零食', [4000, 8000], [6000, 12000], [12000, 24000], 'g'),
|
||
I('餐椅和儿童餐具', [200, 500], [500, 1200], [1500, 3500], 'g', 0.5),
|
||
I('衣服鞋袜', [2000, 4000], [4000, 8000], [10000, 20000], 'g', 0.8),
|
||
I('玩具和积木', [800, 2000], [2000, 5000], [6000, 15000], 'g', 0.8),
|
||
I('绘本', [300, 800], [800, 2000], [2000, 5000], 'g', 0.8),
|
||
I('滑板车、平衡车', [200, 500], [500, 1200], [1500, 4000], 'g', 0.6),
|
||
I('防撞角、护栏等安全用品', [100, 300], [300, 800], [800, 2000], 'g', 0.5),
|
||
I(['自费疫苗(流感等)', '自费疫苗', '自费疫苗全打'], [300, 800], [1500, 3000], [3000, 5000], '0'),
|
||
I(['儿保体检', '儿保体检', '私立儿科体检'], [300, 600], [600, 1500], [3000, 8000], 'm'),
|
||
I(['看病和医药', '看病和医药', '私立儿科看病'], [1000, 3000], [2000, 5000], [6000, 15000], 'm'),
|
||
I(['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [800, 1200], [1600, 4000], [16000, 30000], '0'),
|
||
D('extra', ['绘本和遛娃', '早教班(每周 1~2 节)', '早教和游泳、音乐等兴趣班'], [[500, 2000], [8000, 16000], [25000, 50000]], 's'),
|
||
I('游乐场和亲子游', [1000, 3000], [4000, 8000], [15000, 30000], '0'),
|
||
I('理发、照片和生日', [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 岁
|
||
[
|
||
D('school', ['普惠幼儿园保教费', '民办幼儿园学费', '双语/国际幼儿园学费'], [[15000, 36000], [60000, 120000], [180000, 360000]], '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('school', ['接送交通', '校车', '校车'], [[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'),
|
||
I('日常饮食和零食', [9000, 18000], [18000, 30000], [36000, 72000], 'g'),
|
||
I('衣服鞋袜', [3000, 6000], [6000, 12000], [15000, 30000], 'g'),
|
||
I('玩具和绘本', [2000, 4000], [4000, 8000], [10000, 20000], 'g'),
|
||
I(['看病和医药', '看病和医药', '私立儿科看病'], [2000, 4000], [4000, 8000], [12000, 25000], 'm'),
|
||
I(['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [1200, 1800], [2400, 6000], [24000, 45000], '0'),
|
||
I(['周边游玩', '每年一两次旅行', '出境亲子游'], [3000, 8000], [12000, 24000], [45000, 90000], '0'),
|
||
I('生日和节日礼物', [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('school', ['上下学交通', '校车', '校车'], [[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'),
|
||
I(['假期托管班', '夏令营和研学', '海外游学和研学营'], [1000, 4000], [10000, 24000], [50000, 120000], '0'),
|
||
I('平板、手表、学习机', [1500, 3000], [3000, 6000], [8000, 15000], 'g'),
|
||
I('日常饮食', [24000, 36000], [36000, 60000], [72000, 120000], 'g'),
|
||
I('衣服鞋袜', [6000, 12000], [12000, 24000], [30000, 60000], 'g'),
|
||
I('零花钱', [1000, 3000], [3000, 8000], [8000, 20000], '0'),
|
||
I(['看病和医药', '看病和医药', '私立医疗'], [3000, 6000], [6000, 12000], [20000, 40000], 'm'),
|
||
I('近视防控和看牙', [1000, 3000], [3000, 8000], [10000, 25000], 'm'),
|
||
I(['少儿医保', '少儿医保和医疗险', '高端医疗险和重疾险'], [2400, 3600], [4800, 12000], [48000, 90000], '0'),
|
||
I(['假期游玩', '家庭旅行', '出境家庭游'], [5000, 12000], [20000, 40000], [80000, 160000], '0'),
|
||
I('生日和节日', [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('school', ['上下学交通', '校车或交通', '校车'], [[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'),
|
||
I(['假期游玩', '研学和夏令营', '海外游学'], [1000, 4000], [8000, 18000], [30000, 80000], '0'),
|
||
I('手机和电脑', [2000, 4000], [4000, 8000], [10000, 20000], 'g'),
|
||
I('日常饮食', [15000, 24000], [24000, 36000], [45000, 75000], 'g'),
|
||
I('衣服鞋子', [4000, 8000], [8000, 15000], [20000, 40000], 'g'),
|
||
I('零花钱', [1500, 4000], [4000, 9000], [10000, 25000], '0'),
|
||
I(['看病和医药', '看病和医药', '私立医疗'], [1500, 3000], [3000, 6000], [10000, 20000], 'm'),
|
||
I('眼镜和牙齿', [1500, 4000], [4000, 10000], [15000, 30000], 'm'),
|
||
I(['医保', '医保和医疗险', '高端医疗险和重疾险'], [1200, 1800], [2400, 6000], [24000, 45000], '0'),
|
||
I(['周边游玩', '家庭旅行', '出境家庭游'], [3000, 8000], [10000, 20000], [40000, 80000], '0'),
|
||
I('生日和节日', [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'),
|
||
I(['假期游玩', '研学', '海外游学'], [1000, 3000], [5000, 12000], [30000, 80000], '0'),
|
||
I('电脑和电子设备', [2000, 5000], [5000, 10000], [10000, 20000], 'g'),
|
||
I('交通和回家路费', [1000, 3000], [2000, 5000], [5000, 12000], 's'),
|
||
I('在家饮食和营养', [9000, 15000], [15000, 24000], [30000, 50000], 'g'),
|
||
I('衣服鞋子', [4000, 8000], [8000, 15000], [20000, 40000], 'g'),
|
||
I('零花钱', [3000, 6000], [6000, 12000], [15000, 30000], '0'),
|
||
I(['看病和医药', '看病和医药', '私立医疗'], [1500, 3000], [3000, 6000], [10000, 20000], 'm'),
|
||
I(['医保', '医保和医疗险', '高端医疗险和重疾险'], [1200, 1800], [2400, 6000], [24000, 45000], '0'),
|
||
I(['高考和志愿填报', '高考、志愿和升学咨询', '留学申请和背景提升'], [1000, 3000], [3000, 10000], [50000, 150000], '0'),
|
||
I('成人礼和毕业旅行', [1000, 3000], [3000, 8000], [10000, 30000], '0')
|
||
]
|
||
];
|
||
function college(s) { // 如果还上大学(不计入合计)
|
||
if (s.school === 2) return { n: '海外本科 4 年', v: [1600000, 3200000] };
|
||
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, msgI = 0, 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;
|
||
return { T: tierOf(r), city: a.city == null ? 1 : a.city, kid: a.kid || 0,
|
||
care: a.care == null ? d.care : a.care, school: a.school == null ? d.school : a.school, extra: a.extra == null ? d.extra : a.extra };
|
||
}
|
||
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 { v = d.v[s.T]; name = Array.isArray(d.n) ? d.n[s.T] : 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 };
|
||
}
|
||
|
||
/* ================= 屏切换 ================= */
|
||
function show(id) {
|
||
[].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; delete A.care; delete A.school; delete A.extra; } // 换路线:后几题回到新路线的默认
|
||
markRoute();
|
||
});
|
||
$('#goQ').addEventListener('click', function () { if (route < 0) return; cur = 0; renderQ(); });
|
||
$('#goLast').addEventListener('click', function () { renderRes(false); });
|
||
|
||
/* ================= 问题屏 ================= */
|
||
function flow() { var st = sel(); return QS.filter(function (q) { return !q.if || q.if(st); }); }
|
||
function val(k) { if (A[k] != null) return A[k]; var d = ROUTES[route].def; return d[k] != null ? d[k] : 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' + (q.two ? ' two' : '');
|
||
opts.innerHTML = q.o.map(function (o, i) {
|
||
var isDef = ROUTES[route].def[q.k] === 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>路线默认</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 && ROUTES[route].def[q.k] != 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 sexW = A.sex === 1 ? '准爸爸' : '准妈妈', 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>0' + (route + 1) + ' ' + R.who + ' · ' + R.how + '</span></div>' +
|
||
'<div><span>家庭</span><span>' + CITY_S[s.city] + ' · ' + (s.kid ? '二胎' : '头胎') + ' · ' + sexW + '</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;
|
||
|
||
// 情感收尾
|
||
$('#momH').textContent = A.sex === 1 ? '你就要做爸爸了。' : '你就要做妈妈了。';
|
||
var p = isAll ? '你的妈妈在养你长大的路上,也花了这么多钱。放在今天,差不多也是这样一张 <em>' + w(b.m) + ' 万</em>的账单;'
|
||
: '光是' + esc(bill.range) + ',就要 <em>' + w(b.m) + ' 万</em>。你的妈妈把你从怀孕一路养到 18 岁,放在今天,差不多是一张 <em>' + w(full.m) + ' 万</em>的账单;';
|
||
var fi = incomeOf(full);
|
||
if (fi) p += '相当于你家现在 <em>' + fi.txt + '</em>的收入;';
|
||
p += '还有数不清的时间和心思,<b>没有一项写在账单上</b>。';
|
||
$('#momP').innerHTML = p;
|
||
renderMsg();
|
||
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);
|
||
}
|
||
|
||
/* ================= 给妈妈的话 ================= */
|
||
function msgs() {
|
||
var who = A.sex === 1 ? '爸爸' : '妈妈';
|
||
return [
|
||
'妈,我今天算了算养一个孩子要花多少钱,才知道你当年有多不容易。辛苦了。',
|
||
'妈,我也快当' + who + '了。以前没好好说过,这些年辛苦你了,谢谢你把我养这么大。',
|
||
'妈,钱能算出来,你给我的时间和心思算不出来。辛苦了,周末回家吃饭。'
|
||
];
|
||
}
|
||
function renderMsg() {
|
||
var L = msgs();
|
||
msgI = msgI % L.length;
|
||
$('#msgQ').textContent = L[msgI];
|
||
$('#msgDt').innerHTML = L.map(function (x, i) { return '<i class="' + (i === msgI ? 'on' : '') + '"></i>'; }).join('');
|
||
}
|
||
$('#msgSw').addEventListener('click', function () { msgI++; renderMsg(); save(); });
|
||
|
||
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);
|
||
}
|
||
function copy(text) {
|
||
function legacy() { return new Promise(function (ok, no) {
|
||
var ta = document.createElement('textarea');
|
||
ta.value = text; ta.setAttribute('readonly', ''); ta.style.cssText = 'position:fixed;top:-999px;opacity:0';
|
||
document.body.appendChild(ta); ta.select();
|
||
var r = false; try { r = document.execCommand('copy'); } catch (e) {}
|
||
document.body.removeChild(ta);
|
||
r ? ok() : no();
|
||
}); }
|
||
if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text).catch(legacy);
|
||
return legacy();
|
||
}
|
||
$('#copyBtn').addEventListener('click', function () {
|
||
copy(msgs()[msgI]).then(function () { toast('已复制,去微信发给妈妈吧'); }, function () { toast('复制没成功,点「生成卡片」发图给她也很好'); });
|
||
});
|
||
|
||
/* ================= 生成卡片:canvas → <img> 长按保存 ================= */
|
||
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();
|
||
}
|
||
function wrap(c, text, maxW) {
|
||
var lines = [], line = '';
|
||
text.split('').forEach(function (ch) {
|
||
if (c.measureText(line + ch).width > maxW && line) { lines.push(line); line = ch; } else line += ch;
|
||
});
|
||
if (line) lines.push(line);
|
||
for (var i = 1; i < lines.length; i++) if (/^[,。、;:!?」]/.test(lines[i])) { lines[i - 1] += lines[i][0]; lines[i] = lines[i].slice(1); }
|
||
return lines.filter(function (l) { return l; });
|
||
}
|
||
function fit(c, text, maxW, size, weight, fam) { // 字太长就缩小
|
||
do { c.font = weight + ' ' + size + 'px ' + fam; size -= 2; } while (c.measureText(text).width > maxW && size > 16);
|
||
}
|
||
function drawCard() {
|
||
var b = bill, R = ROUTES[route], W = 750, H = 1240, inc = incomeOf(b.full);
|
||
var cv = document.createElement('canvas'); cv.width = W; cv.height = H;
|
||
var c = cv.getContext('2d');
|
||
var g = c.createLinearGradient(0, 0, 0, H);
|
||
g.addColorStop(0, '#FFF3E6'); g.addColorStop(.5, '#FFFBF6'); g.addColorStop(1, '#EAF7F0');
|
||
c.fillStyle = g; c.fillRect(0, 0, W, H);
|
||
c.fillStyle = 'rgba(255,255,255,.6)'; c.beginPath(); c.arc(660, 70, 200, 0, 7); c.fill();
|
||
c.fillStyle = 'rgba(24,160,88,.06)'; c.beginPath(); c.arc(40, 1120, 170, 0, 7); c.fill();
|
||
c.textAlign = 'center'; c.textBaseline = 'alphabetic';
|
||
c.font = '700 24px ' + FONT;
|
||
var lb = '兜爸心选 · 养娃账单', lw = c.measureText(lb).width + 48;
|
||
c.fillStyle = '#fff'; rr(c, (W - lw) / 2, 54, lw, 48, 24); c.fill();
|
||
c.fillStyle = '#18A058'; c.fillText(lb, W / 2, 86);
|
||
c.fillStyle = '#1F2329'; c.font = '800 54px ' + FONT;
|
||
c.fillText(A.sex === 1 ? '你就要做爸爸了。' : '你就要做妈妈了。', W / 2, 190);
|
||
c.fillStyle = '#86909C'; c.font = '400 25px ' + FONT;
|
||
c.fillText(R.who + ' · ' + R.how + ' · ' + CITY_S[b.s.city], W / 2, 238);
|
||
/* 小票:始终画「怀孕到 18 岁」的全程合计,和下面「你的妈妈也付过这样一张账单」对上 */
|
||
var f = b.full, px = 110, py = 286, pw = W - 220, ph = inc ? 440 : 400;
|
||
c.save(); c.shadowColor = 'rgba(120,80,40,.14)'; c.shadowBlur = 30; c.shadowOffsetY = 10;
|
||
c.fillStyle = '#fff'; c.beginPath(); c.moveTo(px, py); c.lineTo(px + pw, py); c.lineTo(px + pw, py + ph);
|
||
var n = Math.floor(pw / 20);
|
||
for (var i = 0; i < n; i++) { var x0 = px + pw - i * (pw / n); c.lineTo(x0 - pw / n / 2, py + ph + 12); c.lineTo(x0 - pw / n, py + ph); }
|
||
c.closePath(); c.fill(); c.restore();
|
||
c.fillStyle = '#86909C'; c.font = '500 23px ' + FONT; c.fillText('养一个孩子 · 怀孕到 18 岁 · 合计', W / 2, py + 62);
|
||
c.fillStyle = '#18A058'; fit(c, '¥' + money(f.m), pw - 40, 66, '800', MONO); c.fillText('¥' + money(f.m), W / 2, py + 142);
|
||
c.fillStyle = '#4E5969'; c.font = '400 23px ' + FONT; c.fillText('约 ' + w(f.m) + ' 万 · 区间 ' + wr(f.lo, f.hi), W / 2, py + 186);
|
||
c.strokeStyle = '#C9CED4'; c.lineWidth = 2; c.setLineDash([8, 7]);
|
||
c.beginPath(); c.moveTo(px + 30, py + 220); c.lineTo(px + pw - 30, py + 220); c.stroke();
|
||
var cols = [[' 1 岁', f.n1], [' 2 岁', f.n2], [' 3 岁', f.n3]], cw = (pw - 60) / 3;
|
||
cols.forEach(function (x, k) {
|
||
var cx = px + 30 + cw * k + cw / 2;
|
||
c.fillStyle = '#86909C'; c.font = '400 21px ' + FONT; c.fillText('养到' + x[0], cx, py + 264);
|
||
c.fillStyle = '#1F2329'; c.font = '800 32px ' + MONO; c.fillText(w(x[1]) + '万', cx, py + 306);
|
||
});
|
||
c.beginPath(); c.moveTo(px + 30, py + 336); c.lineTo(px + pw - 30, py + 336); c.stroke(); c.setLineDash([]);
|
||
var yy = py + 376;
|
||
if (inc) {
|
||
c.textAlign = 'left'; c.fillStyle = '#4E5969'; c.font = '400 22px ' + FONT; c.fillText('≈ 你家现在的收入', px + 30, yy);
|
||
c.textAlign = 'right'; c.fillStyle = '#18A058'; c.font = '800 24px ' + FONT; c.fillText(inc.txt, px + pw - 30, yy);
|
||
yy += 42;
|
||
}
|
||
c.textAlign = 'left'; c.fillStyle = '#B4733A'; c.font = '700 23px ' + FONT; c.fillText('时间、心思和拥抱', px + 30, yy);
|
||
c.textAlign = 'right'; c.fillText('无法计价', px + pw - 30, yy);
|
||
c.textAlign = 'center';
|
||
var oy = inc ? 40 : 0;
|
||
c.fillStyle = '#5B5249'; c.font = '400 28px ' + FONT;
|
||
c.fillText('你的妈妈养你长大,', W / 2, 778 + oy);
|
||
c.fillText('也付过这样一张账单。', W / 2, 822 + oy);
|
||
c.font = '800 60px ' + FONT;
|
||
var say = '妈,辛苦了。', sw = c.measureText(say).width;
|
||
c.fillStyle = 'rgba(244,190,130,.45)'; c.fillRect((W - sw) / 2 - 6, 896 + oy, sw + 12, 24);
|
||
c.fillStyle = '#1F2329'; c.fillText(say, W / 2, 914 + oy);
|
||
c.fillStyle = '#4E5969'; c.font = '400 26px ' + FONT;
|
||
var ls = wrap(c, '「' + msgs()[msgI] + '」', W - 170);
|
||
ls.slice(0, 3).forEach(function (l, k) { c.fillText(l, W / 2, 986 + oy + k * 40); });
|
||
c.strokeStyle = 'rgba(24,160,88,.25)'; c.lineWidth = 2;
|
||
c.beginPath(); c.moveTo(W / 2 - 150, 1148); c.lineTo(W / 2 + 150, 1148); c.stroke();
|
||
c.fillStyle = '#18A058'; c.font = '700 25px ' + FONT; c.fillText('兜爸心选', W / 2, 1188);
|
||
c.fillStyle = '#86909C'; c.font = '400 20px ' + FONT; c.fillText('孕期专题 · 生娃计算器 · 估算仅供参考', W / 2, 1218);
|
||
return cv.toDataURL('image/png');
|
||
}
|
||
var isWx = /MicroMessenger/i.test(navigator.userAgent);
|
||
$('#cardBtn').addEventListener('click', function () {
|
||
if (!bill) return;
|
||
try {
|
||
openMask(drawCard(), '给妈妈的卡片', '给妈妈的卡片.png', '长按图片,保存或发给妈妈', false);
|
||
} catch (e) { toast('生成失败,截个图保存也可以'); }
|
||
});
|
||
/* ================= 保存账单图片:把整张小票原样画成长图(宽 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 sexW = A.sex === 1 ? '准爸爸' : '准妈妈';
|
||
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], ['路线', '0' + (route + 1) + ' ' + R.who + ' · ' + R.how],
|
||
['家庭', CITY_S[s.city] + ' · ' + (s.kid ? '二胎' : '头胎') + ' · ' + sexW], ['范围', 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 === 2 ? '省钱' : '' },
|
||
{ id: 'chanjianrili', why: why[1] },
|
||
{ id: 'bushui', why: why[2] }
|
||
], { title: route === 0 ? '钱花在刀刃上' : '帮你少花冤枉钱', 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');
|
||
});
|
||
}
|
||
|
||
$('#reRoute').addEventListener('click', function () { renderStart(); show('sStart'); });
|
||
$('#reAns').addEventListener('click', function () { cur = 0; renderQ(); });
|
||
|
||
/* ================= 存取(v5;兼容读取 v4:路线 + 6 题答案,阶段按全部、收入为空) ================= */
|
||
function save() {
|
||
try { localStorage.setItem(KEY, JSON.stringify({ route: route, a: A, done: hasBill, no: billNo, at: billAt, msg: msgI })); } catch (e) {}
|
||
}
|
||
function load() {
|
||
try {
|
||
var o = JSON.parse(localStorage.getItem(KEY) || 'null') || JSON.parse(localStorage.getItem(KEY4) || 'null');
|
||
if (!o || !(o.route >= 0 && o.route < ROUTES.length)) return false;
|
||
route = o.route;
|
||
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;
|
||
msgI = +o.msg || 0;
|
||
billNo = o.no || ''; billAt = o.at || '';
|
||
if (!A.st) A.st = ALL.slice();
|
||
var need = QS.filter(function (q) { return q.o && (!q.if || q.if(A.st)); });
|
||
var all = need.every(function (q) { return A[q.k] != null; });
|
||
if (all && !billNo) newBill();
|
||
return !!o.done && all;
|
||
} catch (e) { return false; }
|
||
}
|
||
|
||
hasBill = load();
|
||
renderStart();
|
||
if (hasBill) renderRes(false); else show('sStart');
|
||
|
||
/* 返回:从首页点进来就 back;直接打开(分享 / 刷新)就回同目录的首页,身份原样带回 */
|
||
$('#back').addEventListener('click', function () {
|
||
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>
|
||
</body>
|
||
</html>
|