|
@@ -0,0 +1,799 @@
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="zh-CN">
|
|
|
|
|
+<head>
|
|
|
|
|
+<meta charset="UTF-8">
|
|
|
|
|
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
|
|
|
+<meta name="format-detection" content="telephone=no">
|
|
|
|
|
+<title>PDF链路测试 · 全屋定制报价方案</title>
|
|
|
|
|
+<style>
|
|
|
|
|
+:root{--p:#1F497D;--pa:#2B6CB0;--bg:#F5F7FA;--card:#fff;--bd:#E2E8F0;--tx:#2D3748;--mu:#718096;--ok:#38A169;--wa:#D69E2E;--dg:#E53E3E;--r:12px}
|
|
|
|
|
+*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
|
|
|
|
|
+body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--tx);line-height:1.55;padding-bottom:calc(88px + env(safe-area-inset-bottom))}
|
|
|
|
|
+header{background:linear-gradient(135deg,#1F497D,#2B6CB0);color:#fff;padding:18px 16px 14px}
|
|
|
|
|
+header h1{font-size:17px;font-weight:700}
|
|
|
|
|
+header .sub{font-size:12px;opacity:.85;margin-top:2px}
|
|
|
|
|
+.wrap{padding:12px;max-width:640px;margin:0 auto}
|
|
|
|
|
+.hero{background:var(--card);border-radius:var(--r);padding:16px;box-shadow:0 1px 3px rgba(0,0,0,.08);margin-top:-26px;position:relative;text-align:center}
|
|
|
|
|
+.hero .label{font-size:12px;color:var(--mu)}
|
|
|
|
|
+.hero .price{font-size:38px;font-weight:800;color:var(--p);letter-spacing:-1px;margin:2px 0}
|
|
|
|
|
+.hero .price small{font-size:16px;font-weight:600}
|
|
|
|
|
+.hero .range{font-size:13px;color:var(--mu)}
|
|
|
|
|
+.badge{display:inline-block;font-size:11px;padding:2px 10px;border-radius:20px;background:#EBF8FF;color:var(--pa);border:1px solid #BEE3F8;margin-top:6px}
|
|
|
|
|
+.sec{margin-top:14px}
|
|
|
|
|
+.sec h2{font-size:15px;font-weight:700;margin:0 2px 8px;display:flex;align-items:center;gap:6px}
|
|
|
|
|
+.card{background:var(--card);border-radius:var(--r);box-shadow:0 1px 3px rgba(0,0,0,.06);overflow:hidden}
|
|
|
|
|
+.room{border-bottom:1px solid var(--bd)}
|
|
|
|
|
+.room:last-child{border-bottom:none}
|
|
|
|
|
+.room-h{padding:12px 14px;display:flex;justify-content:space-between;align-items:center;gap:8px}
|
|
|
|
|
+.room-h .nm{font-weight:700;font-size:14px}
|
|
|
|
|
+.room-h .dm{font-size:11px;color:var(--mu)}
|
|
|
|
|
+.room-h .tt{font-weight:800;color:var(--p);font-size:15px;white-space:nowrap}
|
|
|
|
|
+.room-h .conf{font-size:10px;padding:1px 6px;border-radius:8px;background:#FFFAF0;color:#C05621;border:1px solid #FBD38D;flex-shrink:0}
|
|
|
|
|
+.items{padding:0 14px 10px;display:none}
|
|
|
|
|
+.room.open .items{display:block}
|
|
|
|
|
+.item{display:flex;justify-content:space-between;font-size:12.5px;padding:5px 0;color:#4A5568}
|
|
|
|
|
+.item .l span{color:var(--mu);font-size:11px;margin-left:6px}
|
|
|
|
|
+.item .v{white-space:nowrap}
|
|
|
|
|
+.trow{display:flex;justify-content:space-between;padding:8px 14px;font-size:13px}
|
|
|
|
|
+.trow .v{font-weight:700}
|
|
|
|
|
+.trow.total{background:#F0FFF4;color:#276749;font-weight:800;font-size:15px}
|
|
|
|
|
+.trow.dim{color:var(--mu);font-size:12px}
|
|
|
|
|
+.plan-wrap{background:var(--card);border-radius:var(--r);padding:10px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
|
|
|
|
|
+.plan-wrap svg,.plan-img{width:100%;height:auto;display:block;border-radius:8px}
|
|
|
|
|
+.plan-note{font-size:11px;color:var(--mu);text-align:center;margin-top:6px}
|
|
|
|
|
+.selling{background:linear-gradient(135deg,#FFFAF0,#FFF5EB);border:1px solid #FBD38D;border-radius:var(--r);padding:12px 14px}
|
|
|
|
|
+.selling li{font-size:13px;margin:6px 0 6px 18px;color:#744210}
|
|
|
|
|
+.selling h3{font-size:13px;color:#C05621;margin-bottom:4px}
|
|
|
|
|
+.acts .item .v{color:var(--ok)}
|
|
|
|
|
+.ctrl{position:fixed;bottom:0;left:0;right:0;background:#fff;border-top:1px solid var(--bd);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:99;max-width:640px;margin:0 auto}
|
|
|
|
|
+.btn{flex:1;padding:12px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer}
|
|
|
|
|
+.btn.pri{background:var(--p);color:#fff}
|
|
|
|
|
+.btn.sec{background:#EDF2F7;color:var(--tx)}
|
|
|
|
|
+details.dims{background:var(--card);border-radius:var(--r);padding:10px 14px;font-size:12px;color:var(--mu)}
|
|
|
|
|
+details.dims summary{cursor:pointer;font-weight:600;color:var(--tx)}
|
|
|
|
|
+details.dims li{margin:4px 0 4px 18px}
|
|
|
|
|
+.drawer{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:100;display:none}
|
|
|
|
|
+.drawer.open{display:block}
|
|
|
|
|
+.sheet{position:absolute;bottom:0;left:0;right:0;background:#fff;border-radius:16px 16px 0 0;max-height:82vh;overflow:auto;padding:16px 16px calc(20px + env(safe-area-inset-bottom));max-width:640px;margin:0 auto}
|
|
|
|
|
+.sheet h3{font-size:15px;margin-bottom:12px}
|
|
|
|
|
+.frow{margin-bottom:14px}
|
|
|
|
|
+.frow label{font-size:12px;color:var(--mu);display:block;margin-bottom:4px}
|
|
|
|
|
+.frow .opts{display:flex;gap:6px;flex-wrap:wrap}
|
|
|
|
|
+.chip{padding:8px 12px;border-radius:8px;border:1.5px solid var(--bd);background:#fff;font-size:13px;cursor:pointer}
|
|
|
|
|
+.chip.on{border-color:var(--pa);background:#EBF8FF;color:var(--pa);font-weight:700}
|
|
|
|
|
+input[type=range]{width:100%}
|
|
|
|
|
+.cabrow{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px dashed var(--bd);font-size:13px}
|
|
|
|
|
+.cabrow input{width:64px;padding:6px;border:1px solid var(--bd);border-radius:6px;font-size:13px}
|
|
|
|
|
+.cabrow .del{color:var(--dg);font-size:18px;padding:0 6px;cursor:pointer}
|
|
|
|
|
+.cabrow .add{color:var(--ok)}
|
|
|
|
|
+@media(min-width:640px){.hero .price{font-size:44px}}
|
|
|
|
|
+</style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+<header>
|
|
|
|
|
+ <h1>PDF链路测试 · 全屋定制报价方案</h1>
|
|
|
|
|
+ <div class="sub">标准客户方案 · 生成于 8/31 23:49 · 拉迷全屋定制</div>
|
|
|
|
|
+</header>
|
|
|
|
|
+<div class="wrap">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="hero">
|
|
|
|
|
+ <div class="label">全屋定制整装价</div>
|
|
|
|
|
+ <div class="price" id="finalPrice">¥165,500</div>
|
|
|
|
|
+ <div class="range" id="finalRange">谈价参考区间 ¥140,675 ~ ¥190,325</div>
|
|
|
|
|
+ <div id="badges">
|
|
|
|
|
+ <span class="badge" id="bTier">PET材质</span>
|
|
|
|
|
+ <span class="badge" id="bDisc">原价</span>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ <div class="sec">
|
|
|
|
|
+ <h2>🏠 户型与定制布局</h2>
|
|
|
|
|
+ <div class="plan-wrap" id="planWrap"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2710 2330" class="fpc-plan">
|
|
|
|
|
+<g id="fpc-rooms">
|
|
|
|
|
+<g class="fpc-room" data-room="厨房">
|
|
|
|
|
+<rect x="960.0" y="120.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="690.0" x2="1355.0" y2="690.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="405.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">厨房</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="玄关">
|
|
|
|
|
+<rect x="960.0" y="880.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="1450.0" x2="1355.0" y2="1450.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1165.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">玄关</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="餐厅">
|
|
|
|
|
+<rect x="960.0" y="880.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="1450.0" x2="1355.0" y2="1450.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1165.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">餐厅</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="客厅">
|
|
|
|
|
+<rect x="960.0" y="880.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="1450.0" x2="1355.0" y2="1450.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1165.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">客厅</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="主卧">
|
|
|
|
|
+<rect x="120.0" y="1640.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="425.0" y1="2210.0" x2="515.0" y2="2210.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="515.0" y="1925.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">主卧</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="次卧">
|
|
|
|
|
+<rect x="960.0" y="1640.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="2210.0" x2="1355.0" y2="2210.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1925.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">次卧</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="卫生间(公卫)">
|
|
|
|
|
+<rect x="960.0" y="880.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="1450.0" x2="1355.0" y2="1450.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1165.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">卫生间(公卫)</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="卫生间(主卫)">
|
|
|
|
|
+<rect x="960.0" y="120.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="690.0" x2="1355.0" y2="690.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="405.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">卫生间(主卫)</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="衣帽间">
|
|
|
|
|
+<rect x="960.0" y="880.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="1450.0" x2="1355.0" y2="1450.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1165.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">衣帽间</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="生活阳台">
|
|
|
|
|
+<rect x="960.0" y="1640.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="1265.0" y1="2210.0" x2="1355.0" y2="2210.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="1355.0" y="1925.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">生活阳台</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-room" data-room="观景阳台">
|
|
|
|
|
+<rect x="1800.0" y="1640.0" width="790.0" height="570.0" fill="#F7FAFC" stroke="#2D3748" stroke-width="6"/>
|
|
|
|
|
+<line x1="2105.0" y1="2210.0" x2="2195.0" y2="2210.0" stroke="#fff" stroke-width="8"/>
|
|
|
|
|
+<text x="2195.0" y="1925.0" text-anchor="middle" dominant-baseline="middle" font-size="52" fill="#2D3748" font-weight="600">观景阳台</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g id="fpc-cabinets">
|
|
|
|
|
+<g class="fpc-cab" data-cab="次卧衣柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="520.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="580.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">次卧衣柜 2.6×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="玄关鞋柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="240.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="440.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">玄关鞋柜 1.2×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="阳台柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="240.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="440.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">阳台柜 1.2×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="餐厅餐边柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="320.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="480.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">餐厅餐边柜 1.6×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="厨房橱柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="300.0" height="240.0" rx="8" fill="#FEEBC8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="470.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">厨房橱柜 1.5×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="衣帽间">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="300.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="470.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">衣帽间 1.5×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="主卧衣柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="300.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="470.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">主卧衣柜 1.5×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g class="fpc-cab" data-cab="浴室柜">
|
|
|
|
|
+<rect x="320.0" y="320.0" width="300.0" height="240.0" rx="8" fill="#BEE3F8" stroke="#1F497D" stroke-width="5" stroke-dasharray="18 8"/>
|
|
|
|
|
+<text x="470.0" y="440.0" text-anchor="middle" dominant-baseline="middle" font-size="44" fill="#1F497D" font-weight="700">浴室柜 1.5×1.2</text>
|
|
|
|
|
+</g>
|
|
|
|
|
+</g>
|
|
|
|
|
+<g id="fpc-dims">
|
|
|
|
|
+</g>
|
|
|
|
|
+<defs><marker id="arr" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5" orient="auto"><path d="M0,0L10,5L0,10z" fill="#C05621"/></marker></defs>
|
|
|
|
|
+</svg></div><p class="plan-note">📐 AI 结构化重绘示意图(非施工图,以实际量尺为准)</p>
|
|
|
|
|
+ <details class="dims"><summary>图面尺寸标注(1 条,用作交叉验证)</summary><ul><li><b>110㎡</b> — 总建筑面积</li></ul></details>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="sec">
|
|
|
|
|
+ <h2>💰 报价明细 <span style="font-weight:400;font-size:11px;color:var(--mu)">(点击房间展开)</span></h2>
|
|
|
|
|
+ <div class="card" id="roomsCard"><div class="room" data-i="0">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">次卧衣柜</div><div class="dm">2.6m×2.5m</div></div><span class="tt">¥32,500</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>2.6m×2.5m · 投影6.5m²</span></span><span class="v">¥32,500</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="1">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">玄关鞋柜</div><div class="dm">1.2m×2.4m</div></div><span class="tt">¥14,400</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.2m×2.4m · 投影2.88m²</span></span><span class="v">¥14,400</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="2">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">阳台柜</div><div class="dm">1.2m×2.4m</div></div><span class="tt">¥14,400</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.2m×2.4m · 投影2.88m²</span></span><span class="v">¥14,400</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="3">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">餐厅餐边柜</div><div class="dm">1.6m×2.4m</div></div><span class="tt">¥19,200</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.6m×2.4m · 投影3.84m²</span></span><span class="v">¥19,200</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="4">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">厨房橱柜</div><div class="dm">1.5m×2.6m</div></div><span class="tt">¥26,500</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.5m×2.6m · 投影5.3m</span></span><span class="v">¥26,500</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="5">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">衣帽间</div><div class="dm">1.5m×2.6m</div></div><span class="tt">¥19,500</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.5m×2.6m · 投影3.9m²</span></span><span class="v">¥19,500</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="6">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">主卧衣柜</div><div class="dm">1.5m×2.6m</div></div><span class="tt">¥19,500</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.5m×2.6m · 投影3.9m²</span></span><span class="v">¥19,500</span></div></div>
|
|
|
|
|
+ </div><div class="room" data-i="7">
|
|
|
|
|
+ <div class="room-h"><div><div class="nm">浴室柜</div><div class="dm">1.5m×2.6m</div></div><span class="tt">¥19,500</span></div>
|
|
|
|
|
+ <div class="items"><div class="item"><span class="l">柜体(PET材质)<span>1.5m×2.6m · 投影3.9m²</span></span><span class="v">¥19,500</span></div></div>
|
|
|
|
|
+ </div></div>
|
|
|
|
|
+ <div class="card" style="margin-top:8px">
|
|
|
|
|
+ <div class="trow"><span>项目小计</span><span class="v" id="tSub">¥165,500</span></div>
|
|
|
|
|
+ <div class="trow dim"><span id="tDiscLabel">原价</span><span class="v" id="tAfter">¥165,500</span></div>
|
|
|
|
|
+ <div class="trow" id="actRow" style="display:none"><span>活动优惠</span><span class="v" style="color:var(--ok)" id="tAct">-¥0</span></div>
|
|
|
|
|
+ <div class="trow total"><span>到手价</span><span class="v" id="tFinal">¥165,500</span></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="sec">
|
|
|
|
|
+ <div class="selling">
|
|
|
|
|
+ <h3>✨ 为什么选择拉迷(标准客户)</h3>
|
|
|
|
|
+ <ul><li>空间利用率拉满:每一面墙都变成收纳</li><li>全国联保5年,铰链五金终身维护</li><li>设计上门量尺,3D效果图确认后再生产</li></ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="sec" style="font-size:11px;color:var(--mu);line-height:1.7">
|
|
|
|
|
+ 计价说明:柜体按投影面积(宽×高)×材质单价;厨房按计费延米(扣除转角重叠与冰箱位)。本报价为AI辅助估算,实际以现场量尺与正式合同为准。质保与赠送以当期活动政策为准。
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<div class="ctrl">
|
|
|
|
|
+ <button class="btn sec" id="btnAdjust">⚖️ 调整报价</button>
|
|
|
|
|
+ <button class="btn pri" id="btnSave">📞 锁定到店礼</button>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<div class="drawer" id="drawer">
|
|
|
|
|
+ <div class="sheet">
|
|
|
|
|
+ <h3>⚖️ 销售操作台(客户不可见)</h3>
|
|
|
|
|
+ <div class="frow"><label>客户类型</label><div class="opts" id="optTier"><button class="chip " data-v="price">🏷️ 价格型</button><button class="chip on" data-v="standard">🏠 标准型</button><button class="chip " data-v="quality">💎 品质型</button></div></div>
|
|
|
|
|
+ <div class="frow"><label>材质档位</label><div class="opts" id="optMat"><button class="chip " data-v="双饰面">双饰面</button><button class="chip on" data-v="PET">PET</button><button class="chip " data-v="混油">混油</button></div></div>
|
|
|
|
|
+ <div class="frow"><label>报价浮动 <span id="floatV">15</span>%</label><input type="range" id="floatR" min="10" max="20" step="1" value="15"></div>
|
|
|
|
|
+ <div class="frow"><label>折扣</label><div class="opts" id="optDisc">
|
|
|
|
|
+ <button class="chip" data-v="1.00">原价</button>
|
|
|
|
|
+ <button class="chip" data-v="0.95">95折</button>
|
|
|
|
|
+ <button class="chip" data-v="0.90">9折</button>
|
|
|
|
|
+ <button class="chip" data-v="0.85">85折</button>
|
|
|
|
|
+ <button class="chip" data-v="0.80">8折</button>
|
|
|
|
|
+ </div></div>
|
|
|
|
|
+ <div class="frow"><label>柜体清单(宽=投影宽 m / 厨房为计费长)</label><div id="cabList"></div>
|
|
|
|
|
+ <div style="margin-top:8px"><button class="chip" id="btnAddCab">+ 增加柜体</button></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="frow"><label>活动优惠</label><div class="opts" id="optAct"></div></div>
|
|
|
|
|
+ <button class="btn pri" style="width:100%" id="btnApply">应用并重算</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+// ============================================================
|
|
|
|
|
+// 拉迷全屋定制 · 默认报价规则库
|
|
|
|
|
+// 数据来源:floorplan-quotation/AI快速报价_Demo2.optimizing.html
|
|
|
|
|
+// (DEFAULT_RULES / Q_CUSTOMER_TIERS / DEFAULT_QUOTATION_PLANS /
|
|
|
|
|
+// CABINET_EXTRA_TEMPLATES / EXTRA_METHOD_MAP / 厨房扣减规则)
|
|
|
|
|
+// 所有价格单位:元。修改价格请改这里的默认值或运行时覆盖 config。
|
|
|
|
|
+// ============================================================
|
|
|
|
|
+
|
|
|
|
|
+const METHOD = {
|
|
|
|
|
+ PROJECTION: '按投影面积', // 元/m² 数量 = 宽(m) × 高(m)
|
|
|
|
|
+ METER: '按延米', // 元/m 数量 = 米数
|
|
|
|
|
+ COUNT: '按个数', // 元/个 数量 = 个数
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 材质三档单价区间(AI快速粗略报价核心)——
|
|
|
|
|
+const PRICE_TIERS = [
|
|
|
|
|
+ { name: '双饰面', min: 3000, max: 3500 },
|
|
|
|
|
+ { name: 'PET', min: 4500, max: 5000 },
|
|
|
|
|
+ { name: '混油', min: 6000, max: 7000 },
|
|
|
|
|
+];
|
|
|
|
|
+const DEFAULT_TIER = 'PET';
|
|
|
|
|
+
|
|
|
|
|
+// —— 计价方式 ——
|
|
|
|
|
+const METHOD_OPTIONS = [
|
|
|
|
|
+ { value: '按投影面积', unit: '元/m²' },
|
|
|
|
|
+ { value: '按个数', unit: '元/个' },
|
|
|
|
|
+ { value: '按延米', unit: '元/m' },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// —— 柜体→空间 映射(识别命名用)——
|
|
|
|
|
+const CABINET_TO_SPACE = {
|
|
|
|
|
+ '主卧衣柜': '主卧', '次卧衣柜': '次卧', '儿童房衣柜': '儿童房', '老人房衣柜': '老人房',
|
|
|
|
|
+ '衣柜': '卧室', '客厅电视柜': '客厅', '电视柜': '客厅', '厨房橱柜': '厨房', '橱柜': '厨房',
|
|
|
|
|
+ '开放式厨房': '开放式厨房', '餐厅餐边柜': '餐厅', '餐边柜': '餐厅', '书房书柜': '书房',
|
|
|
|
|
+ '书柜': '书房', '玄关鞋柜': '玄关', '鞋柜': '玄关', '玄关柜': '玄关', '阳台柜': '阳台',
|
|
|
|
|
+ '衣帽间': '衣帽间', '储物柜': '储物间', '酒柜': '餐厅', '浴室柜': '卫生间', '榻榻米': '次卧',
|
|
|
|
|
+ '展示柜': '客厅', '洗衣机柜': '阳台', '卡座': '餐厅', '飘窗柜': '飘窗',
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const SPACE_TO_DEFAULT_CABINET = {
|
|
|
|
|
+ '主卧': '主卧衣柜', '次卧': '次卧衣柜', '儿童房': '儿童房衣柜', '老人房': '老人房衣柜',
|
|
|
|
|
+ '客厅': '客厅电视柜', '厨房': '厨房橱柜', '开放式厨房': '开放式厨房', '餐厅': '餐厅餐边柜',
|
|
|
|
|
+ '客餐厅': '餐厅餐边柜', '书房': '书房书柜', '玄关': '玄关鞋柜', '阳台': '阳台柜',
|
|
|
|
|
+ '衣帽间': '衣帽间', '储物间': '储物柜', '卫生间': '浴室柜', '主卫': '浴室柜',
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 柜体类型 / 布局 / 门板 预设 ——
|
|
|
|
|
+const CUSTOM_TYPES_PRESET = {
|
|
|
|
|
+ cabinetType: ['主卧衣柜','次卧衣柜','儿童房衣柜','老人房衣柜','客厅电视柜','厨房橱柜','开放式厨房','餐厅餐边柜','书房书柜','玄关鞋柜','阳台柜','衣帽间','储物柜','酒柜','浴室柜','榻榻米'],
|
|
|
|
|
+ layoutForm: ['一字型','L型','U型','二字型','不规则'],
|
|
|
|
|
+ doorPanelType: ['普通平板','风影门板','PET门板','实木贴皮','烤漆门板','吸塑门板','玻璃门','百叶门','无门板'],
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 柜体默认附件模板(识别到柜型后自动配附件)——
|
|
|
|
|
+const CABINET_EXTRA_TEMPLATES = {
|
|
|
|
|
+ '玄关': ['灯带','变压器+开关','自弹器','见光板','铰链'],
|
|
|
|
|
+ '鞋柜': ['灯带','变压器+开关','自弹器','见光板','铰链','换鞋凳'],
|
|
|
|
|
+ '电视柜': ['自弹器','见光板','铰链'],
|
|
|
|
|
+ '展示柜': ['灯带','变压器+开关','拉手','见光板','铰链','自弹器'],
|
|
|
|
|
+ '酒柜': ['灯带','变压器+开关','拉手','见光板','铰链','自弹器','玻璃门升级'],
|
|
|
|
|
+ '洗衣机': ['自弹器','护墙板','见光板','铰链'],
|
|
|
|
|
+ '阳台': ['自弹器','见光板','铰链'],
|
|
|
|
|
+ '餐边': ['抽屉','拉手','自弹器','金属拉手','灯带','变压器+开关','见光板','铰链'],
|
|
|
|
|
+ '次卧': ['衣杆','金属拉手','抽屉','见光板','铰链'],
|
|
|
|
|
+ '儿童房': ['衣杆','金属拉手','抽屉','见光板','铰链'],
|
|
|
|
|
+ '书房': ['衣杆','金属拉手','抽屉','见光板','铰链'],
|
|
|
|
|
+ '主卧': ['金属拉手','抽屉','衣杆','自弹器','圆弧','铰链'],
|
|
|
|
|
+ '衣帽间': ['金属拉手','抽屉','衣杆','自弹器','圆弧','铰链','裤架领带格','拉篮'],
|
|
|
|
|
+ '厨房': ['见光板','碗篮','锅篮','自弹器'],
|
|
|
|
|
+ '_default': ['自弹器','见光板','铰链'],
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 附件计价方式映射 ——
|
|
|
|
|
+const EXTRA_METHOD_MAP = {
|
|
|
|
|
+ '见光板': '按投影面积', '护墙板': '按投影面积', '玻璃门升级': '按投影面积',
|
|
|
|
|
+ '灯带': '按延米', '衣杆': '按延米', '石材台面': '按延米',
|
|
|
|
|
+ '自弹器': '按个数', '铰链': '按个数', '变压器+开关': '按个数', '拉手': '按个数',
|
|
|
|
|
+ '金属拉手': '按个数', '抽屉': '按个数', '裤架领带格': '按个数', '拉篮': '按个数',
|
|
|
|
|
+ '碗篮': '按个数', '锅篮': '按个数', '圆弧': '按个数', '换鞋凳': '按个数',
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 基础价格表(name → {price, unit, method})——
|
|
|
|
|
+const PRICE_BOOK = {
|
|
|
|
|
+ // 柜体
|
|
|
|
|
+ '柜体投影单价': { price: 1852, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '生态澳松板柜体': { price: 1852, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '铝格栅板柜体': { price: 2532, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ // 厨房(单件计价模式)
|
|
|
|
|
+ '地柜': { price: 2380, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '吊柜': { price: 1748, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '冰箱吊柜': { price: 2622, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '高柜': { price: 5950, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '台面': { price: 1480, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '台面超出部分': { price: 2480, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '碗篮': { price: 1268, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '锅篮': { price: 1628, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '切角': { price: 500, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '台下盆': { price: 500, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '包管': { price: 500, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ // 门板单价
|
|
|
|
|
+ '标配门板': { price: 380, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '烤漆门板': { price: 580, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '吸塑门板': { price: 480, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '实木门板': { price: 880, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '铝格栅门板': { price: 680, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ 'PET门板': { price: 450, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ // 附件
|
|
|
|
|
+ '见光板': { price: 1138, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '护墙板': { price: 1852, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '玻璃门升级': { price: 1852, unit: '元/m²', method: '按投影面积' },
|
|
|
|
|
+ '灯带': { price: 386, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '变压器+开关': { price: 536, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '自弹器': { price: 115, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '铰链': { price: 40, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '拉手': { price: 226, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '金属拉手': { price: 225, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '抽屉': { price: 1300, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '衣杆': { price: 288, unit: '元/m', method: '按延米' },
|
|
|
|
|
+ '裤架领带格': { price: 0, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '拉篮': { price: 0, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '圆弧': { price: 1560, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '换鞋凳': { price: 0, unit: '元/个', method: '按个数' },
|
|
|
|
|
+ '石材台面': { price: 1480, unit: '元/m', method: '按延米' },
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 厨房计价参数(qGetKitchenBillableMeters 同源)——
|
|
|
|
|
+const KITCHEN_RULES = {
|
|
|
|
|
+ packageMeters: 3, // 3米基础套餐(单件计价模式的扣减基准)
|
|
|
|
|
+ overlapMm: 600, // 转角重叠扣减
|
|
|
|
|
+ fridgeMm: 1000, // 冰箱位预留
|
|
|
|
|
+ typeThresholdM: 2, // 最小边 ≤2m 判 L 型,>2m 判 U 型
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 客户分型(销售策略核心)——
|
|
|
|
|
+const CUSTOMER_TIERS = {
|
|
|
|
|
+ price: {
|
|
|
|
|
+ label: '看重价格型', icon: '🏷️',
|
|
|
|
|
+ strategy: '预算少的客户直接按性价比报,快速赢单',
|
|
|
|
|
+ defaultPlan: 'economy', defaultDiscount: 0.85,
|
|
|
|
|
+ doorType: '标配', hardware: '经济实用款', service: '标准测量+安装',
|
|
|
|
|
+ warranty: '3年质保', gifts: '送基础五金配件', panelLabel: '进口爱格板(加价升级)',
|
|
|
|
|
+ autoEnableActivity: true,
|
|
|
|
|
+ discounts: [
|
|
|
|
|
+ { id: 'p1', label: '投影面积85折', rate: 0.85, deletable: false },
|
|
|
|
|
+ { id: 'p2', label: '投影面积8折特惠', rate: 0.80, deletable: true },
|
|
|
|
|
+ { id: 'p3', label: '团购78折', rate: 0.78, deletable: true },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ standard: {
|
|
|
|
|
+ label: '标准客户', icon: '🏠',
|
|
|
|
|
+ strategy: '标准配置报价,正常谈单空间',
|
|
|
|
|
+ defaultPlan: 'standard', defaultDiscount: 1.0,
|
|
|
|
|
+ doorType: '标配门板', hardware: '国产优质铰链/拉手', service: '标准测量安装',
|
|
|
|
|
+ warranty: '5年质保', gifts: '可参与活动优惠', panelLabel: '国产E0级颗粒板',
|
|
|
|
|
+ autoEnableActivity: false,
|
|
|
|
|
+ discounts: [
|
|
|
|
|
+ { id: 's1', label: '原价', rate: 1.00, deletable: false },
|
|
|
|
|
+ { id: 's2', label: '投影面积95折', rate: 0.95, deletable: true },
|
|
|
|
|
+ { id: 's3', label: '投影面积9折', rate: 0.90, deletable: true },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ quality: {
|
|
|
|
|
+ label: '品质导向型', icon: '💎',
|
|
|
|
|
+ strategy: '有预算的客户报高一点,留谈价空间,突出配置与质感',
|
|
|
|
|
+ defaultPlan: 'quality', defaultDiscount: 1.0,
|
|
|
|
|
+ doorType: '烤漆/吸塑高端门板', hardware: '进口百隆/海蒂诗五金', service: 'VIP专属设计师+金牌安装',
|
|
|
|
|
+ warranty: '10年质保+终身维护', gifts: '赠灯带+抽屉升级包', panelLabel: '进口爱格板/可丽芙(推荐)',
|
|
|
|
|
+ premiumRate: 1.15,
|
|
|
|
|
+ autoEnableActivity: false,
|
|
|
|
|
+ discounts: [
|
|
|
|
|
+ { id: 'q1', label: '投影面积95折(保利润)', rate: 0.95, deletable: false },
|
|
|
|
|
+ { id: 'q2', label: '原价(高端不打折)', rate: 1.00, deletable: true },
|
|
|
|
|
+ { id: 'q3', label: 'VIP专享92折', rate: 0.92, deletable: true },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 报价方案 ——
|
|
|
|
|
+const QUOTATION_PLANS = [
|
|
|
|
|
+ { id: 'economy', name: '经济型方案', description: '适合预算敏感型客户,主打性价比', customerTier: 'price', tierName: '双饰面', floatPct: 10, discountRate: 0.98 },
|
|
|
|
|
+ { id: 'standard', name: '标准型方案', description: '默认方案,标准配置,适合大多数客户', customerTier: 'standard', tierName: 'PET', floatPct: 15, discountRate: 1.00 },
|
|
|
|
|
+ { id: 'quality', name: '品质型方案', description: '高端配置,适合追求品质的客户', customerTier: 'quality', tierName: '混油', floatPct: 15, discountRate: 1.00 },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// —— 营销活动模块(可勾选叠加)——
|
|
|
|
|
+const ACTIVITY_MODULES = [
|
|
|
|
|
+ { id: 'deposit5000', type: 'deposit', name: '定金膨胀(5千档)', desc: '交¥5,000→膨胀¥8,000 + ¥500抵用券', amount: 3500, minOrder: 30000 },
|
|
|
|
|
+ { id: 'deposit10000', type: 'deposit', name: '定金膨胀(1万档)', desc: '交¥10,000→膨胀¥13,000 + ¥800抵用券', amount: 3800, minOrder: 30000 },
|
|
|
|
|
+ { id: 'coupon20k', type: 'coupon', name: '满减券', desc: '满¥20,000减¥1,000', amount: 1000, threshold: 20000 },
|
|
|
|
|
+ { id: 'cashback', type: 'tiered_cashback', name: '阶梯返现', tiers: [
|
|
|
|
|
+ { min: 50000, cash: 2000 }, { min: 100000, cash: 5000 }, { min: 150000, cash: 8000 },
|
|
|
|
|
+ ] },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// —— 默认层高与房间面积先验(识别失败兜底估算用)——
|
|
|
|
|
+const DEFAULTS = {
|
|
|
|
|
+ height: 2.8, // 默认柜高(层高)
|
|
|
|
|
+ roomAreaPriors: { // 常见房间典型面积范围 [min, max](m²)
|
|
|
|
|
+ '主卧': [12, 22], '次卧': [8, 16], '儿童房': [8, 14], '老人房': [9, 15], '书房': [6, 12],
|
|
|
|
|
+ '客厅': [18, 35], '餐厅': [8, 18], '厨房': [4.5, 10], '卫生间': [2.5, 6], '主卫': [3, 6],
|
|
|
|
|
+ '公卫': [2.5, 6], '阳台': [3, 10], '玄关': [2, 6], '衣帽间': [3, 10], '储物间': [1.5, 5],
|
|
|
|
|
+ },
|
|
|
|
|
+ cabinetWallRatio: { // 柜体默认占所在墙面比例(无明确尺寸时的估算)
|
|
|
|
|
+ '主卧衣柜': 0.55, '次卧衣柜': 0.50, '儿童房衣柜': 0.50, '玄关鞋柜': 0.6,
|
|
|
|
|
+ '客厅电视柜': 0.55, '餐厅餐边柜': 0.5, '阳台柜': 0.6, '书房书柜': 0.5,
|
|
|
|
|
+ },
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// —— 赢单文案素材(业主视角价值描述)——
|
|
|
|
|
+const SELLING_POINTS = {
|
|
|
|
|
+ price: [
|
|
|
|
|
+ '按投影面积计价,明明白白,没有隐形收费',
|
|
|
|
|
+ 'E0级环保板材,装完即可安心入住',
|
|
|
|
|
+ '工厂直供价,同样的预算多做一个柜子',
|
|
|
|
|
+ ],
|
|
|
|
|
+ standard: [
|
|
|
|
|
+ '空间利用率拉满:每一面墙都变成收纳',
|
|
|
|
|
+ '全国联保5年,铰链五金终身维护',
|
|
|
|
|
+ '设计上门量尺,3D效果图确认后再生产',
|
|
|
|
|
+ ],
|
|
|
|
|
+ quality: [
|
|
|
|
|
+ '进口板材+进口五金,用20年不变形不褪色',
|
|
|
|
|
+ 'VIP设计师一对一,全屋风格统一设计',
|
|
|
|
|
+ '10年质保+终身维护,一次装修终身省心',
|
|
|
|
|
+ ],
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================
|
|
|
|
|
+// 拉迷全屋定制 · 报价引擎(纯函数,浏览器/CLI 共用)
|
|
|
|
|
+// 两种模式:
|
|
|
|
|
+// quick — AI快速粗略报价:投影面积/厨房延米 × 三档材质区间单价
|
|
|
|
|
+// detail — 明细报价:Excel 预算表模型(柜体+门板补差+附件逐项)
|
|
|
|
|
+// 销售策略:客户分型 → 方案/折扣/浮动 → 活动叠加 → 谈价区间
|
|
|
|
|
+// ============================================================
|
|
|
|
|
+
|
|
|
|
|
+const r2 = (v) => Math.round(v * 100) / 100;
|
|
|
|
|
+const num = (v, d = 0) => { const n = parseFloat(v); return Number.isFinite(n) ? n : d; };
|
|
|
|
|
+
|
|
|
|
|
+// ---------- 材质档位 ----------
|
|
|
|
|
+function getTier(name) {
|
|
|
|
|
+ const t = PRICE_TIERS.find((x) => x.name === (name || DEFAULT_TIER));
|
|
|
|
|
+ return t || PRICE_TIERS[1];
|
|
|
|
|
+}
|
|
|
|
|
+// 浮动百分比(10~15)在区间内线性插值 → 单价(qGetAiUnitPrice 同源逻辑)
|
|
|
|
|
+function tierUnitPrice(tier, floatPct = 15) {
|
|
|
|
|
+ const k = Math.min(1, Math.max(0, (num(floatPct, 15) - 10) / 5));
|
|
|
|
|
+ return r2(tier.min + k * (tier.max - tier.min));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---------- 厨房计费米数(qGetKitchenBillableMeters 同源)----------
|
|
|
|
|
+function kitchenBillable(walls = [], rules = {}) {
|
|
|
|
|
+ const R = { ...KITCHEN_RULES, ...rules };
|
|
|
|
|
+ const ws = (Array.isArray(walls) ? walls : []).map((v) => num(v)).filter((v) => v > 0);
|
|
|
|
|
+ if (!ws.length) return null;
|
|
|
|
|
+ const total = ws.reduce((a, b) => a + b, 0);
|
|
|
|
|
+ const minWall = Math.min(...ws);
|
|
|
|
|
+ const maxWall = Math.max(...ws);
|
|
|
|
|
+ const type = minWall <= R.typeThresholdM ? 'L' : 'U';
|
|
|
|
|
+ const overlapCount = type === 'L' ? 1 : 2;
|
|
|
|
|
+ const usedLen = type === 'L' ? maxWall + minWall : maxWall + maxWall + minWall;
|
|
|
|
|
+ const billable = Math.max(0, usedLen - (overlapCount * R.overlapMm) / 1000 - R.fridgeMm / 1000);
|
|
|
|
|
+ return {
|
|
|
|
|
+ walls: ws, total: r2(total), minWall: r2(minWall), maxWall: r2(maxWall),
|
|
|
|
|
+ kitchenType: type, overlapCount, fridgeMm: R.fridgeMm, usedLen: r2(usedLen), billable: r2(billable),
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+const isKitchen = (name = '') => /厨房|橱柜/.test(String(name));
|
|
|
|
|
+
|
|
|
|
|
+// ---------- 附件数量启发式(Excel 模型总结,均可编辑) ----------
|
|
|
|
|
+function estimateExtras(cabinet) {
|
|
|
|
|
+ const w = num(cabinet.width, 1.5);
|
|
|
|
|
+ const h = num(cabinet.height, DEFAULTS.height);
|
|
|
|
|
+ const type = String(cabinet.type || '');
|
|
|
|
|
+ let tplKey = Object.keys(CABINET_EXTRA_TEMPLATES).find((k) => k !== '_default' && type.includes(k));
|
|
|
|
|
+ if (!tplKey && isKitchen(type)) tplKey = '厨房';
|
|
|
|
|
+ const names = CABINET_EXTRA_TEMPLATES[tplKey || '_default'] || CABINET_EXTRA_TEMPLATES._default;
|
|
|
|
|
+ const sections = h > 1.6 ? 2 : 1; // 高柜分上下段
|
|
|
|
|
+ const doorsPerSection = Math.max(1, Math.round(w / 0.45));
|
|
|
|
|
+ const doors = sections * doorsPerSection;
|
|
|
|
|
+ const hingePerDoor = h > 2 ? 3 : 2;
|
|
|
|
|
+ const out = [];
|
|
|
|
|
+ for (const name of names) {
|
|
|
|
|
+ const pb = PRICE_BOOK[name];
|
|
|
|
|
+ if (!pb) continue;
|
|
|
|
|
+ let qty = 0; let note = '';
|
|
|
|
|
+ switch (name) {
|
|
|
|
|
+ case '铰链': qty = doors * hingePerDoor; note = `${doors}门×${hingePerDoor}铰链`; break;
|
|
|
|
|
+ case '自弹器': qty = Math.max(1, doors - 1); note = '反弹门配'; break;
|
|
|
|
|
+ case '见光板': qty = 1; note = '单侧见光(0.5m宽)'; break;
|
|
|
|
|
+ case '灯带': qty = r2(w * 2); note = '顶底双层'; break;
|
|
|
|
|
+ case '变压器+开关': qty = 1; note = 'LED电源'; break;
|
|
|
|
|
+ case '衣杆': qty = r2(w); note = '通长挂杆'; break;
|
|
|
|
|
+ case '抽屉': qty = type.includes('主卧') || type.includes('衣帽') ? 3 : 2; break;
|
|
|
|
|
+ case '金属拉手': case '拉手': qty = doors + 2; break;
|
|
|
|
|
+ case '碗篮': case '锅篮': qty = 1; break;
|
|
|
|
|
+ default: qty = name === '玻璃门升级' || name === '护墙板' ? 0 : 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (qty > 0) out.push({ name, method: pb.method, unit: pb.unit, unitPrice: pb.price, qty, note });
|
|
|
|
|
+ }
|
|
|
|
|
+ return out;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---------- 单柜体计价 ----------
|
|
|
|
|
+// 计价数量:投影面积 = 宽×高;厨房 = 计费延米
|
|
|
|
|
+function cabinetQty(cabinet, rules = {}) {
|
|
|
|
|
+ if (isKitchen(cabinet.type)) {
|
|
|
|
|
+ const kb = kitchenBillable(cabinet.walls || [cabinet.width, cabinet.height, cabinet.width, cabinet.height], rules);
|
|
|
|
|
+ return { qty: kb ? kb.billable : num(cabinet.width), unit: 'm', kb };
|
|
|
|
|
+ }
|
|
|
|
|
+ return { qty: r2(num(cabinet.width) * num(cabinet.height)), unit: 'm²', kb: null };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 快速模式:单柜体一行
|
|
|
|
|
+function quickLine(cabinet, { tierName, floatPct = 15, rules } = {}) {
|
|
|
|
|
+ const tier = getTier(tierName);
|
|
|
|
|
+ const U = tierUnitPrice(tier, floatPct);
|
|
|
|
|
+ const { qty, unit, kb } = cabinetQty(cabinet, rules);
|
|
|
|
|
+ return {
|
|
|
|
|
+ mode: 'quick', name: cabinet.type, tierName: tier.name,
|
|
|
|
|
+ qty, unit, unitPrice: U, total: r2(qty * U), kb,
|
|
|
|
|
+ dims: num(cabinet.width) && num(cabinet.height) ? `${num(cabinet.width)}m×${num(cabinet.height)}m` : '',
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 明细模式:单柜体 → Excel 风格明细行
|
|
|
|
|
+function detailLines(cabinet, opts = {}) {
|
|
|
|
|
+ const { doorDiffPrice = PRICE_BOOK['柜体投影单价'].price > 0 ? 538 : 538, rules, includeDoorDiff = true } = opts;
|
|
|
|
|
+ const lines = [];
|
|
|
|
|
+ const isK = isKitchen(cabinet.type);
|
|
|
|
|
+ const { qty } = cabinetQty(cabinet, rules);
|
|
|
|
|
+ if (isK) {
|
|
|
|
|
+ // 厨房单件计价(Excel 模式:地柜+吊柜+台面+功能五金)
|
|
|
|
|
+ const walls = cabinet.walls || [cabinet.width, cabinet.height, cabinet.width, cabinet.height];
|
|
|
|
|
+ const straight = Math.max(0, ...walls); // 直线段米数近似
|
|
|
|
|
+ for (const [name, factor, note] of [
|
|
|
|
|
+ ['地柜', 1, '沿墙地柜'], ['吊柜', 1, '沿墙吊柜'], ['台面', 1, '石英石台面'],
|
|
|
|
|
+ ]) {
|
|
|
|
|
+ const pb = PRICE_BOOK[name];
|
|
|
|
|
+ lines.push({ name, qty: r2(qty), unit: 'm', unitPrice: pb.price, total: r2(qty * pb.price), note, method: pb.method });
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const name of ['碗篮', '锅篮']) {
|
|
|
|
|
+ const pb = PRICE_BOOK[name];
|
|
|
|
|
+ lines.push({ name, qty: 1, unit: '个', unitPrice: pb.price, total: pb.price, note: '标准配置', method: pb.method });
|
|
|
|
|
+ }
|
|
|
|
|
+ return { cabinet, isKitchen: true, lines, subtotal: r2(lines.reduce((s, l) => s + l.total, 0)) };
|
|
|
|
|
+ }
|
|
|
|
|
+ const pb = PRICE_BOOK['生态澳松板柜体'];
|
|
|
|
|
+ lines.push({ name: '柜体', qty, unit: 'm²', unitPrice: pb.price, total: r2(qty * pb.price), note: '生态澳松板', method: pb.method });
|
|
|
|
|
+ if (includeDoorDiff && doorDiffPrice > 0) {
|
|
|
|
|
+ lines.push({ name: '板门补差价', qty, unit: 'm²', unitPrice: doorDiffPrice, total: r2(qty * doorDiffPrice), note: '双饰面门补差', method: pb.method });
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const ex of estimateExtras(cabinet)) {
|
|
|
|
|
+ const total = ex.method === '按投影面积' ? r2(ex.qty * 0.5 * num(cabinet.height) * ex.unitPrice)
|
|
|
|
|
+ : ex.method === '按延米' ? r2(ex.qty * ex.unitPrice)
|
|
|
|
|
+ : r2(ex.qty * ex.unitPrice);
|
|
|
|
|
+ lines.push({ name: ex.name, qty: ex.qty, unit: ex.unit, unitPrice: ex.unitPrice, total, note: ex.note, method: ex.method });
|
|
|
|
|
+ }
|
|
|
|
|
+ return { cabinet, isKitchen: false, lines, subtotal: r2(lines.reduce((s, l) => s + l.total, 0)) };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---------- 报价主入口 ----------
|
|
|
|
|
+// inputs: { cabinets:[{type,width,height,walls?,excluded?}], tier, floatPct, discount, tierKey(客户分型), planId, mode }
|
|
|
|
|
+function buildQuote(input = {}) {
|
|
|
|
|
+ const {
|
|
|
|
|
+ cabinets = [], mode = 'quick',
|
|
|
|
|
+ tierName = input.tier || DEFAULT_TIER,
|
|
|
|
|
+ floatPct = 15, discount = null, tierKey = 'standard', planId = null,
|
|
|
|
|
+ rules = {}, extras = [], // extras: [{name, room, qty, unitPrice}] 手动增删项
|
|
|
|
|
+ clientName = '', note = '',
|
|
|
|
|
+ } = input;
|
|
|
|
|
+
|
|
|
|
|
+ const plan = QUOTATION_PLANS.find((p) => p.id === (planId || CUSTOMER_TIERS[tierKey]?.defaultPlan)) || QUOTATION_PLANS[1];
|
|
|
|
|
+ const custTier = CUSTOMER_TIERS[tierKey] || CUSTOMER_TIERS.standard;
|
|
|
|
|
+ const effDiscount = discount != null ? num(discount) : (custTier.defaultDiscount ?? plan.discountRate ?? 1);
|
|
|
|
|
+
|
|
|
|
|
+ const rooms = cabinets
|
|
|
|
|
+ .filter((c) => !c.excluded)
|
|
|
|
|
+ .map((c, i) => {
|
|
|
|
|
+ const base = mode === 'detail' ? detailLines(c, { rules }) : { ...quickLine(c, { tierName, floatPct, rules }), lines: null };
|
|
|
|
|
+ const id = c.id || `cab_${i + 1}`;
|
|
|
|
|
+ return { id, type: c.type, dims: base.dims || `${num(c.width)}m×${num(c.height)}m`, ...base };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const itemsSubtotal = r2(rooms.reduce((s, r) => s + (r.subtotal ?? r.total), 0));
|
|
|
|
|
+ const manualExtras = extras.map((e, i) => ({
|
|
|
|
|
+ id: e.id || `ext_${i + 1}`, name: e.name, qty: num(e.qty, 1), unit: e.unit || '项',
|
|
|
|
|
+ unitPrice: num(e.unitPrice), total: r2(num(e.qty, 1) * num(e.unitPrice)), kind: e.kind || 'add',
|
|
|
|
|
+ }));
|
|
|
|
|
+ const extrasTotal = r2(manualExtras.reduce((s, e) => s + (e.kind === 'remove' ? -e.total : e.total), 0));
|
|
|
|
|
+
|
|
|
|
|
+ const premiumRate = custTier.premiumRate || 1;
|
|
|
|
|
+ const afterPremium = r2((itemsSubtotal + extrasTotal) * premiumRate);
|
|
|
|
|
+ const afterDiscount = r2(afterPremium * effDiscount);
|
|
|
|
|
+
|
|
|
|
|
+ // 活动叠加(自动/手动)
|
|
|
|
|
+ const acts = (input.activities || (custTier.autoEnableActivity ? ACTIVITY_MODULES.map((m) => m.id) : []))
|
|
|
|
|
+ .map((id) => ACTIVITY_MODULES.find((m) => m.id === id)).filter(Boolean);
|
|
|
|
|
+ let activityTotal = 0;
|
|
|
|
|
+ const activityLines = [];
|
|
|
|
|
+ for (const m of acts) {
|
|
|
|
|
+ let amt = 0; let desc = m.desc || m.name;
|
|
|
|
|
+ if (m.type === 'tiered_cashback') {
|
|
|
|
|
+ const hit = [...(m.tiers || [])].sort((a, b) => b.min - a.min).find((t) => afterDiscount >= t.min);
|
|
|
|
|
+ if (!hit) continue;
|
|
|
|
|
+ amt = hit.cash; desc = `满¥${hit.min.toLocaleString()}返¥${hit.cash.toLocaleString()}`;
|
|
|
|
|
+ } else if (m.type === 'coupon' && afterDiscount < (m.threshold || 0)) continue;
|
|
|
|
|
+ else if (m.type === 'deposit' && afterDiscount < (m.minOrder || 30000)) continue;
|
|
|
|
|
+ else amt = m.amount || 0;
|
|
|
|
|
+ activityTotal += amt;
|
|
|
|
|
+ activityLines.push({ id: m.id, name: m.name, desc, amount: amt });
|
|
|
|
|
+ }
|
|
|
|
|
+ const finalPrice = Math.max(0, r2(afterDiscount - activityTotal));
|
|
|
|
|
+
|
|
|
|
|
+ // 谈价区间(浮动率)
|
|
|
|
|
+ const rate = num(floatPct, 15) / 100;
|
|
|
|
|
+ const range = { low: Math.round(finalPrice * (1 - rate)), high: Math.round(finalPrice * (1 + rate)) };
|
|
|
|
|
+
|
|
|
|
|
+ // 业主视角价值点
|
|
|
|
|
+ const selling = SELLING_POINTS[tierKey] || SELLING_POINTS.standard;
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ meta: {
|
|
|
|
|
+ clientName, note: note || custTier.strategy, mode,
|
|
|
|
|
+ tierName: mode === 'quick' ? getTier(tierName).name : undefined,
|
|
|
|
|
+ floatPct, planId: plan.id, planName: plan.name,
|
|
|
|
|
+ customerTier: tierKey, customerLabel: custTier.label,
|
|
|
|
|
+ discount: effDiscount, premiumRate: premiumRate !== 1 ? premiumRate : undefined,
|
|
|
|
|
+ generatedAt: new Date().toISOString(),
|
|
|
|
|
+ },
|
|
|
|
|
+ rooms, manualExtras, activityLines,
|
|
|
|
|
+ totals: {
|
|
|
|
|
+ itemsSubtotal, extrasTotal, premiumRate, afterPremium, discount: effDiscount,
|
|
|
|
|
+ afterDiscount, activityTotal, finalPrice, range,
|
|
|
|
|
+ discountLabel: custTier.discounts?.find((d) => Math.abs(d.rate - effDiscount) < 0.001)?.label || `折扣${Math.round(effDiscount * 100)}%`,
|
|
|
|
|
+ },
|
|
|
|
|
+ selling,
|
|
|
|
|
+ config: { tierKey, tierName, floatPct, discount: effDiscount, planId: plan.id, mode },
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---------- 语义化调整应用(销售边聊边改) ----------
|
|
|
|
|
+// ops: [{op:'add'|'remove'|'resize'|'rename'|'price', target, ...}]
|
|
|
|
|
+function applyOps(cabinets, ops = []) {
|
|
|
|
|
+ const out = cabinets.map((c) => ({ ...c }));
|
|
|
|
|
+ for (const op of ops) {
|
|
|
|
|
+ if (op.op === 'add') {
|
|
|
|
|
+ out.push({ type: op.type || op.name || '柜体', width: num(op.width, 1.5), height: num(op.height, DEFAULTS.height), from: '语音新增' });
|
|
|
|
|
+ } else if (op.op === 'remove') {
|
|
|
|
|
+ const i = out.findIndex((c) => c.type === op.target);
|
|
|
|
|
+ if (i >= 0) out.splice(i, 1);
|
|
|
|
|
+ } else if (op.op === 'resize') {
|
|
|
|
|
+ const c = out.find((c) => c.type === op.target);
|
|
|
|
|
+ if (c) { if (op.width) c.width = num(op.width); if (op.height) c.height = num(op.height); c.from = '手动调整'; }
|
|
|
|
|
+ } else if (op.op === 'rename') {
|
|
|
|
|
+ const c = out.find((c) => c.type === op.target);
|
|
|
|
|
+ if (c) c.type = op.to || c.type;
|
|
|
|
|
+ } else if (op.op === 'exclude') {
|
|
|
|
|
+ const c = out.find((c) => c.type === op.target);
|
|
|
|
|
+ if (c) c.excluded = true;
|
|
|
|
|
+ } else if (op.op === 'include') {
|
|
|
|
|
+ const c = out.find((c) => c.type === op.target);
|
|
|
|
|
+ if (c) delete c.excluded;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return out;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 授权范围内校验(浮动区间即授权边界)
|
|
|
|
|
+function withinAuthority(quote, price) {
|
|
|
|
|
+ const { range } = quote.totals;
|
|
|
|
|
+ return price >= range.low && price <= range.high;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+var __DEFAULT__ = { buildQuote, applyOps, quickLine, detailLines, kitchenBillable, tierUnitPrice, getTier, withinAuthority };
|
|
|
|
|
+
|
|
|
|
|
+window.__Q__ = {"cabinets":[{"id":"cab_1","type":"次卧衣柜","width":2.6,"height":2.6,"rawName":"次卧衣柜"},{"id":"cab_2","type":"玄关鞋柜","width":1.2,"height":2.6,"rawName":"玄关鞋柜"},{"id":"cab_3","type":"阳台柜","width":1.2,"height":2.6,"rawName":"阳台柜"},{"id":"cab_4","type":"餐厅餐边柜","width":1.6,"height":2.6,"rawName":"餐厅餐边柜"},{"id":"cab_5","type":"厨房橱柜","height":2.6,"rawName":"厨房橱柜"},{"id":"cab_6","type":"衣帽间","width":1.5,"height":2.6,"rawName":"衣帽间"},{"id":"cab_7","type":"主卧衣柜","width":1.5,"height":2.6,"rawName":"主卧衣柜"},{"id":"cab_8","type":"浴室柜","width":1.5,"height":2.6,"rawName":"浴室柜"}],"config":{"tierKey":"standard","tierName":"PET","floatPct":15,"discount":1,"planId":"standard","mode":"quick"},"salesNote":""};
|
|
|
|
|
+console.log('fpc engine ready');
|
|
|
|
|
+</script>
|
|
|
|
|
+<script>
|
|
|
|
|
+(function(){
|
|
|
|
|
+ var Q = window.__Q__; var S = document; var money = function(n){ return '¥' + Math.round(n||0).toLocaleString('zh-CN'); };
|
|
|
|
|
+ var cfg = Object.assign({ tierKey:'standard', tierName:'PET', floatPct:15, discount:1.0, mode:'quick' }, Q.config||{});
|
|
|
|
|
+ function calc(){ return buildQuote({ cabinets: Q.cabinets, tierKey: cfg.tierKey, tierName: cfg.tierName, floatPct: cfg.floatPct, discount: cfg.discount, mode: cfg.mode, clientName: "PDF链路测试" }); }
|
|
|
|
|
+ function refresh(){
|
|
|
|
|
+ var q = calc(); var t = q.totals;
|
|
|
|
|
+ S.getElementById('finalPrice').textContent = money(t.finalPrice);
|
|
|
|
|
+ S.getElementById('finalRange').textContent = '谈价参考区间 ' + money(t.range.low) + ' ~ ' + money(t.range.high);
|
|
|
|
|
+ S.getElementById('tSub').textContent = money(t.itemsSubtotal);
|
|
|
|
|
+ S.getElementById('tDiscLabel').textContent = t.discountLabel;
|
|
|
|
|
+ S.getElementById('tAfter').textContent = money(t.afterDiscount);
|
|
|
|
|
+ S.getElementById('tAct').textContent = '-' + money(t.activityTotal);
|
|
|
|
|
+ S.getElementById('tFinal').textContent = money(t.finalPrice);
|
|
|
|
|
+ S.getElementById('bTier').textContent = (q.meta.tierName||'') + '材质';
|
|
|
|
|
+ S.getElementById('bDisc').textContent = t.discountLabel;
|
|
|
|
|
+ }
|
|
|
|
|
+ // room expand
|
|
|
|
|
+ S.querySelectorAll('.room-h').forEach(function(el){ el.addEventListener('click', function(){ el.parentElement.classList.toggle('open'); }); });
|
|
|
|
|
+ // chips
|
|
|
|
|
+ function bindChips(id, fn){ var box = S.getElementById(id); if(!box) return; box.addEventListener('click', function(e){ var b = e.target.closest('.chip'); if(!b) return; box.querySelectorAll('.chip').forEach(function(x){x.classList.remove('on');}); b.classList.add('on'); fn(b.dataset.v); }); }
|
|
|
|
|
+ bindChips('optTier', function(v){ cfg.tierKey = v; refresh(); });
|
|
|
|
|
+ bindChips('optMat', function(v){ cfg.tierName = v; refresh(); });
|
|
|
|
|
+ bindChips('optDisc', function(v){ cfg.discount = parseFloat(v); refresh(); });
|
|
|
|
|
+ var fr = S.getElementById('floatR'); fr.addEventListener('input', function(){ S.getElementById('floatV').textContent = fr.value; cfg.floatPct = parseInt(fr.value); refresh(); });
|
|
|
|
|
+ // drawer
|
|
|
|
|
+ var d = S.getElementById('drawer');
|
|
|
|
|
+ S.getElementById('btnAdjust').addEventListener('click', function(){ d.classList.add('open'); renderCabs(); });
|
|
|
|
|
+ d.addEventListener('click', function(e){ if(e.target === d) d.classList.remove('open'); });
|
|
|
|
|
+ function renderCabs(){
|
|
|
|
|
+ var box = S.getElementById('cabList'); box.innerHTML = '';
|
|
|
|
|
+ Q.cabinets.forEach(function(c, i){
|
|
|
|
|
+ var row = S.createElement('div'); row.className = 'cabrow';
|
|
|
|
|
+ var dims = String(c.width||'') ? String(c.width) : '';
|
|
|
|
|
+ row.innerHTML = '<span style="flex:1">' + c.rawName + '</span>' +
|
|
|
|
|
+ '<input type="number" step="0.05" value="' + (c.width||1.5) + '" data-i="' + i + '" data-f="width" title="宽(米)">' +
|
|
|
|
|
+ '<span class="del" data-i="' + i + '">✕</span>';
|
|
|
|
|
+ box.appendChild(row);
|
|
|
|
|
+ });
|
|
|
|
|
+ box.onclick = function(e){
|
|
|
|
|
+ var del = e.target.closest('.del');
|
|
|
|
|
+ if (del) { Q.cabinets.splice(parseInt(del.dataset.i),1); renderCabs(); refresh(); return; }
|
|
|
|
|
+ var inp = e.target.closest('input');
|
|
|
|
|
+ if (inp) { inp.addEventListener('change', function(){ Q.cabinets[parseInt(inp.dataset.i)].width = parseFloat(inp.value)||1.5; refresh(); }); }
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ S.getElementById('btnAddCab').addEventListener('click', function(){ Q.cabinets.push({ type:'储物柜', rawName:'储物柜', width:1.5, height:2.6 }); renderCabs(); refresh(); });
|
|
|
|
|
+ S.getElementById('btnSave').addEventListener('click', function(){
|
|
|
|
|
+ alert('已记录!销售将带着这份报价与您确认到店时间,到店凭此单领取到店礼。');
|
|
|
|
|
+ });
|
|
|
|
|
+ refresh();
|
|
|
|
|
+})();
|
|
|
|
|
+</script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|