集装箱房配置报价计算器
选择下方选项配置您的房屋,获取实时报价
正在加载配置模板…
const CURR_SYMBOL={'RMB':'¥','USD':'$'};const CURR_LABEL={'RMB':'人民币','USD':'美元'};const EXCHANGE_RATE=7.2;const FOUNDATION_GROUPS=['Foundation - Basic','Foundation - Enhanced Insulation','Foundation - Larger Space (Open Top)','Foundation - Flat Roof','Foundation - AU N3 Flat Roof'];let currentSize='20ft';let currentQuoteId=null;const GROUP_LABELS={"基础架构":"基础架构","Foundation - Basic":"基础架构(标准)","Foundation - Enhanced Insulation":"基础架构(增强保温)","Foundation - Larger Space (Open Top)":"基础架构(大空间开顶)","Foundation - Flat Roof":"基础架构(平顶)","Foundation - AU N3 Flat Roof":"基础架构(澳标N3平顶)","2米凉亭":"凉亭","凉亭":"凉亭","门":"门","窗户":"窗户","隔断":"隔断","卫生间":"卫生间","地板革":"地板(Vinyl + SPC)","单板":"屋顶顶板","坡屋顶":"坡屋顶","橱柜":"厨房橱柜","木地板":"木地板","锁扣地板":"锁扣地板","欧标":"欧标","澳标":"澳标","澳标紫皮线":"澳标紫皮线","玻璃幕2.3*2.3":"玻璃幕墙2.3×2.3","玻璃幕(通用)":"玻璃幕墙(按平米)","省力机构":"省力机构","真石漆":"真石漆","美标/澳标/欧标电配":"电配(UL/SAA/CE)","踢脚线":"踢脚线","金属雕花板":"金属雕花板","集成墙板":"集成墙板","雨水槽":"雨水槽",};
const OPTION_LABELS={"10ftD+75W":"10尺双翼75mm墙","20ftD+75W":"20尺双翼75mm墙","30ftD+75W":"30尺双翼75mm墙","40ftD+75W":"40尺双翼75mm墙","20ftS+75W":"20尺单翼75mm墙","30ftS+75W":"30尺单翼75mm墙","40ftS+75W":"40尺单翼75mm墙","20ftDN(700)+75W":"20尺700走廊双翼75mm","30ftDN(700)+75W":"30尺700走廊双翼75mm","40ftDN(700)+75W":"40尺700走廊双翼75mm","[Insulated] 20ftD+100W":"20尺双翼100mm增强保温","[Insulated] 30ftD+100W":"30尺双翼100mm增强保温","[Insulated] 40ftD+100W":"40尺双翼100mm增强保温","[Insulated] 20ftD++100PUW":"20尺双翼PU100mm","[Insulated] 30ftD++100PUW":"30尺双翼PU100mm","[Insulated] 40ftD++100PUW":"40尺双翼PU100mm","[Insulated] 20ftD+75PUW":"20尺双翼PU75mm","[Insulated] 30ftD+75PUW":"30尺双翼PU75mm","[Insulated] 40ftD+75PUW":"40尺双翼PU75mm","[OpenTop] 20ftD+75W+H":"20尺双翼加高开顶","[OpenTop] 30ftD+75W+H":"30尺双翼加高开顶","[OpenTop] 40ftD+75W+H":"40尺双翼加高开顶","[FlatRoof] 20ftD-FlatRoof":"20尺平顶双翼","[FlatRoof] 30ftD-FlatRoof":"30尺平顶双翼","[FlatRoof] 40ftD-FlatRoof":"40尺平顶双翼","[FlatRoof] 20ftS-FlatRoof":"20尺平顶单翼","[FlatRoof] 30ftS-FlatRoof":"30尺平顶单翼","[FlatRoof] 40ftS-FlatRoof":"40尺平顶单翼","[FlatRoof] 20ftD-FlatRoof-IH2.4":"20尺平顶双翼内高2.4m","[FlatRoof] 30ftD-FlatRoof-IH2.4":"30尺平顶双翼内高2.4m","[FlatRoof] 40ftD-FlatRoof-IH2.4":"40尺平顶双翼内高2.4m","AU N3 20ft Flat Roof":"澳标N3 20尺平顶 ¥56,050","AU N3 30ft Flat Roof":"澳标N3 30尺平顶 ¥80,750","AU N3 40ft Flat Roof":"澳标N3 40尺平顶 ¥104,000","断桥铝平开门_宽1890_高2230":"断桥铝平开门1890×2230","断桥铝推拉门_宽1890_高2230":"断桥铝推拉门1890×2230","钛镁合金平开门_宽1890_高2230":"钛镁合金平开门1890×2230","钛镁合金推拉门_宽1890_高2230":"钛镁合金推拉门1890×2230","钛镁合金推拉门_宽1890_高2230_带自吸":"钛镁合金推拉门(带自吸)","肯德基门_宽1890_高2230":"肯德基门1890×2230","单扇肯德基门_宽900_高2230":"单扇肯德基门900×2230","单开钢质门_宽980_高1980":"单开钢质门980×1980","双开钢质门_宽1740_高1970_鑫彤门":"钢质双开门1740×1970","防盗门_宽1890_高2230_盼盼":"盼盼防盗门1890×2230","塑钢双玻推拉窗带纱窗_930_930":"塑钢双玻推拉窗+纱窗930×930","断桥铝双玻窗_金刚网纱窗_外悬或者平开_930_930":"断桥铝双玻窗+金刚网纱窗930×930","断桥铝双玻窗_推拉_930_930":"断桥铝双玻推拉窗930×930","断桥双平开窗_930_930":"断桥双平开窗930×930","铝合金双玻推拉窗带纱窗":"铝合金双玻推拉窗+纱窗","定制断桥铝窗户":"定制断桥铝窗户(按平米计价)","金刚网纱窗":"金刚网纱窗","一室_50mm_eps墙板":"一室隔断EPS 50mm","一室_50mm厚pu墙板":"一室隔断PU 50mm",
"干湿分离卫生间_宽1330_长2200_1":"干湿分离卫浴#1 1330×2200","干湿分离卫生间_宽1330_长2200_2":"干湿分离卫浴#2 1330×2200","带洗衣机卫生间_宽1330_长2800":"卫生间+洗衣机 1330×2800","带橱柜卫生间_宽1330_长2800":"卫生间+橱柜 1330×2800","地板革_20尺2_0mm":"地板革20尺 2.0mm","平顶外顶部加单板_20尺":"平顶加单板20尺","20尺加人字屋顶_50":"人字屋顶20尺 50mm","20尺加人字屋顶_75":"人字屋顶20尺 75mm","20尺加人字屋顶_100":"人字屋顶20尺 100mm","厨房_类型1_柜门是吸塑门_台面可以是不锈钢或者人造大理石_不锈钢水盆":"厨房类型1-吸塑门+不锈钢水盆","厨房_类型1_柜门是吸塑门_台面可以是不锈钢或者人造大理石_黑色手工盆":"厨房类型1-吸塑门+黑色手工盆","厨房_类型2_柜门是吸塑门_台面可以是不锈钢或者人造大理石_不锈钢水盆":"厨房类型2-吸塑门+不锈钢水盆","厨房_类型2_柜门是吸塑门_台面可以是不锈钢或者人造大理石_黑色手工盆":"厨房类型2-吸塑门+黑色手工盆","厨房_类型3_柜门是吸塑门_台面可以是不锈钢或者人造大理石_默认不锈钢水盆_黑色":"厨房类型3-吸塑门+不锈钢水盆","厨房_类型4_柜门是pet材质_台面建议使用人造大理石":"厨房类型4-PET门板","木地板_20尺12_0mm":"木地板20尺 12mm","spc地板20尺3mm":"SPC地板20尺 3mm","ce认证吸顶灯":"CE认证吸顶灯","saa认证马卡龙吸顶灯":"SAA马卡龙吸顶灯","20尺":"20尺标准","幕墙_侧墙一小扇2_3_2_3":"玻璃幕墙2.3×2.3","按平米计算":"按平米计价","空气弹簧杆":"空气弹簧杆","金属雕花板_20尺_真石漆":"金属雕花板20尺+真石漆","金属雕花板_加高20尺_真石漆":"金属雕花板加高20尺+真石漆","ul_saa_ce认证电配_20尺":"电配20尺 UL/SAA/CE","踢脚线20尺":"踢脚线20尺","金属雕花板_20尺":"金属雕花板20尺","金属雕花板_20尺加高":"金属雕花板20尺加高","换集成墙板20尺":"集成墙板20尺","雨水收集器_落水管20尺":"雨水槽20尺","凉亭按平米计价":"凉亭(按平米计价 ¥580/㎡)","仅塑木地板露台":"塑木地板(按平米)","露台栏杆":"露台栏杆(按米)","露台屋顶":"露台屋顶(按平米)",};let templateData=[];let templateName='';const selections={};function getCurr(){const r=document.querySelector('input[name=currency]:checked');return r?r.value:'RMB'}
async function loadTemplate(){try{const res=await fetch('/api/config/public/groups/b93458d4-290f-4d86-9ff8-eb6f6ac4b2b3');const groups=await res.json();const tplRes=await fetch('/api/config/public/templates');const tpls=await tplRes.json();const tpl=tpls.find(t=>t.id==='b93458d4-290f-4d86-9ff8-eb6f6ac4b2b3');templateName=tpl?tpl.name:'Expandable House 20ft+通用';document.getElementById('templateName').textContent=templateName;groups.sort((a,b)=>(a.sort_order||0)-(b.sort_order||0));templateData=groups;renderGroups();updateSummary();loadStateFromURL();document.getElementById('loading').style.display='none';document.getElementById('content').style.display='block'}catch(e){document.getElementById('loading').innerHTML='
'}}
function fmtPrice(rmbAmt,curr){const sym=CURR_SYMBOL[curr]||'¥';if(curr==='USD')return sym+Math.round(rmbAmt/EXCHANGE_RATE).toLocaleString();return sym+rmbAmt.toLocaleString()}
function setSize(size){currentSize=size;document.querySelectorAll('.size-btn').forEach(b=>{b.classList.toggle('selected',b.dataset.size===size)});document.getElementById('customSizeInput').style.display=size==='custom'?'block':'none';renderGroups();updateSummary();saveStateToURL();if(currentQuoteId)saveQuote(!0);} function saveStateToURL(){const data={s:currentSize,o:{}};for(const g of templateData){const sel=selections[g.id];if(!sel)continue;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';if(isMulti&&Array.isArray(sel)&&sel.length>0){data.o[g.id]=sel.map(oid=>{const o=g.options.find(x=>x.id===oid);return{id:oid,q:qtyValues[oid]||1}})}else if(!isMulti&&sel){data.o[g.id]={id:sel,q:qtyValues[sel]||1}}} const hash=btoa(encodeURIComponent(JSON.stringify(data)));history.replaceState(null,'','#'+hash)}
function loadStateFromURL(){const hash=location.hash.slice(1);if(!hash)return;try{const data=JSON.parse(decodeURIComponent(atob(hash)));if(data.s&&data.s!==currentSize){currentSize=data.s} document.querySelectorAll('.size-btn').forEach(b=>{b.classList.toggle('selected',b.dataset.size===currentSize)});document.getElementById('customSizeInput').style.display=currentSize==='custom'?'block':'none';for(const g of templateData){const sel=data.o[g.id];if(!sel)continue;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';if(isMulti&&Array.isArray(sel)){selections[g.id]=sel.map(x=>x.id);sel.forEach(x=>{if(x.q)qtyValues[x.id]=x.q})}else if(!isMulti&&sel){selections[g.id]=sel.id;if(sel.q)qtyValues[sel.id]=sel.q}} renderGroups();updateSummary();for(const g of templateData){const sel=selections[g.id];if(sel===undefined||sel===null||(Array.isArray(sel)&&sel.length===0))continue;document.querySelectorAll('.opt-btn[data-gid="'+g.id+'"]').forEach(b=>{if(Array.isArray(sel))b.classList.toggle('selected',sel.includes(b.dataset.oid));else b.classList.toggle('selected',b.dataset.oid===sel)})}}catch(e){}}
function getQuotes(){try{return JSON.parse(localStorage.getItem('huaying_quotes')||'[]')}catch(e){return[]}}
function saveQuotes(qs){localStorage.setItem('huaying_quotes',JSON.stringify(qs))}
function collectQuoteData(){const data={s:currentSize,o:{}};for(const g of templateData){const sel=selections[g.id];if(!sel)continue;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';if(isMulti&&Array.isArray(sel)&&sel.length>0){data.o[g.id]=sel.map(oid=>{const o=g.options.find(x=>x.id===oid);return{id:oid,q:qtyValues[oid]||1}})}else if(!isMulti&&sel){data.o[g.id]={id:sel,q:qtyValues[sel]||1}}} return data}
function calcTotal(){let total=0;for(const g of templateData){const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';const sel=selections[g.id];if(isMulti&&Array.isArray(sel)) sel.forEach(oid=>{const o=g.options.find(x=>x.id===oid);if(o)total+=(o.price_adjustment||0)*(qtyValues[oid]||1);});else if(!isMulti&&sel){const o=g.options.find(x=>x.id===sel);if(o)total+=(o.price_adjustment||0)*(qtyValues[sel]||1);}} return total}
function updateQuoteStatus(){const bar=document.getElementById('quoteStatusBar');const icon=document.getElementById('quoteStatusIcon');const text=document.getElementById('quoteStatusText');const nameDisp=document.getElementById('quoteNameDisplay');if(currentQuoteId){bar.style.display='block';icon.textContent='🟢';text.textContent='已保存';nameDisp.textContent=getQuotes().find(q=>q.id===currentQuoteId)?.name||''}else{bar.style.display='block';icon.textContent='🔴';text.textContent='未保存';nameDisp.textContent=''} const btn=document.getElementById('saveBtn');btn.textContent=currentQuoteId?'🔄 更新报价单':'💾 保存报价单'}
function saveQuote(quiet){const data=collectQuoteData();const total=calcTotal();const quotes=getQuotes();const now=Date.now();
if(currentQuoteId){const idx=quotes.findIndex(q=>q.id===currentQuoteId);if(idx>=0){quotes[idx].total=total;quotes[idx].data=data;quotes[idx].date=new Date().toLocaleString('zh-CN');quotes[idx].templateName=templateName}}else{const name='报价单 '+(quotes.length+1);currentQuoteId=now;quotes.push({id:now,name:name,date:new Date().toLocaleString('zh-CN'),total:total,data:data,templateName:templateName})} saveQuotes(quotes);updateQuoteStatus();if(!quiet){const hint=document.getElementById('requiredHint');hint.textContent='✅ 报价单已'+(currentQuoteId?'更新':'保存');hint.className='required-hint show ok';setTimeout(()=>{hint.textContent='请完成所有必选项';hint.className='required-hint show'},3000)}}
function setCurrentQuote(id){currentQuoteId=id;updateQuoteStatus()}
function showQuoteManager(){const el=document.getElementById('quoteList');const quotes=getQuotes();if(quotes.length===0){el.innerHTML='
'}else{let html='';for(let i=quotes.length-1;i>=0;i--){const q=quotes[i];html+='
';html+='
';html+='
'} el.innerHTML=html} document.getElementById('quoteModal').classList.add('show')}
function closeQuoteManager(){document.getElementById('quoteModal').classList.remove('show')}
function loadQuote(idx){const quotes=getQuotes();const q=quotes[quotes.length-1-idx];if(!q)return;const data=q.data;if(data.s)currentSize=data.s;document.querySelectorAll('.size-btn').forEach(b=>{b.classList.toggle('selected',b.dataset.size===currentSize)});document.getElementById('customSizeInput').style.display=currentSize==='custom'?'block':'none';for(const g of templateData){const sel=data.o[g.id];if(!sel)continue;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';if(isMulti&&Array.isArray(sel)){selections[g.id]=sel.map(x=>x.id);sel.forEach(x=>{if(x.q)qtyValues[x.id]=x.q})}else if(!isMulti&&sel){selections[g.id]=sel.id;if(sel.q)qtyValues[sel.id]=sel.q}} renderGroups();updateSummary();for(const g of templateData){const sel=selections[g.id];if(sel===undefined||sel===null||(Array.isArray(sel)&&sel.length===0))continue;document.querySelectorAll('.opt-btn[data-gid="'+g.id+'"]').forEach(b=>{if(Array.isArray(sel))b.classList.toggle('selected',sel.includes(b.dataset.oid));else b.classList.toggle('selected',b.dataset.oid===sel)})} closeQuoteManager();setCurrentQuote(q.id);const hint=document.getElementById('requiredHint');hint.textContent='✅ 已加载报价单: '+q.name;hint.className='required-hint show ok';setTimeout(()=>{hint.textContent='请完成所有必选项';hint.className='required-hint show'},3000)}
function deleteQuote(idx){if(!confirm('确定删除该报价单?'))return;const quotes=getQuotes();quotes.splice(quotes.length-1-idx,1);saveQuotes(quotes);showQuoteManager()}
function renameQuote(idx){const quotes=getQuotes();const q=quotes[quotes.length-1-idx];if(!q)return;const newName=prompt('输入新名称:',q.name);if(newName&&newName.trim()){q.name=newName.trim();saveQuotes(quotes);showQuoteManager()}}
function filterBySize(optName,optNameCn){if(currentSize==='custom')return!0;const n=optName+' '+optNameCn;const has10=n.includes('10ft')||n.includes('10尺');const has20=n.includes('20ft')||n.includes('20尺');const has30=n.includes('30ft')||n.includes('30尺');const has40=n.includes('40ft')||n.includes('40尺');if(has10||has20||has30||has40){if(currentSize==='10ft'&&has10)return!0;if(currentSize==='20ft'&&has20)return!0;if(currentSize==='30ft'&&has30)return!0;if(currentSize==='40ft'&&has40)return!0;const cnt=[has10,has20,has30,has40].filter(Boolean).length;if(cnt>1)return!0;return!1} return!0}
function renderGroups(){const el=document.getElementById('optionsPanel');let html='';for(const g of templateData){const label=GROUP_LABELS[g.name]||g.name_cn||g.name;const isReq=g.is_required===1||g.is_required===!0;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';if(selections[g.id]===undefined)selections[g.id]=isMulti?[]:null;
const filteredOpts=(g.options||[]).filter(o=>filterBySize(o.name,o.name_cn||''));
if(filteredOpts.length===0)continue;
html+='
';html+='
'} el.innerHTML=html}
function toggle(el){const gid=el.dataset.gid,oid=el.dataset.oid,isMulti=el.dataset.multi==='true';const group=templateData.find(g=>g.id===gid);if(!group)return;
if(FOUNDATION_GROUPS.includes(group.name)){for(const g of templateData){if(FOUNDATION_GROUPS.includes(g.name)&&g.id!==gid){const e=selections[g.id];if(isMulti){if(Array.isArray(e)){e.length=0}}else{selections[g.id]=null} document.querySelectorAll('.opt-btn[data-gid="'+g.id+'"]').forEach(b=>b.classList.remove('selected'))}}}
if(isMulti){const sel=selections[gid]||[];const idx=sel.indexOf(oid);idx>=0?sel.splice(idx,1):sel.push(oid);selections[gid]=sel;document.querySelectorAll('.opt-btn[data-gid="'+gid+'"]').forEach(b=>{b.classList.toggle('selected',sel.includes(b.dataset.oid))})}else{if(selections[gid]===oid){selections[gid]=null}else{selections[gid]=oid} document.querySelectorAll('.opt-btn[data-gid="'+gid+'"]').forEach(b=>{b.classList.toggle('selected',b.dataset.oid===selections[gid])})} const card=el.closest('.card');if(card)card.querySelectorAll('.opt-btn').forEach(b=>b.classList.remove('required-empty'));updateSummary();saveStateToURL();if(currentQuoteId)saveQuote(!0);}
const qtyValues={};
function updateQty(el,ev){ev.stopPropagation();let v=parseFloat(el.value)||1;if(v<0.1)v=0.1;if(v>999)v=999;el.value=v;const oid=el.closest('.opt-btn').dataset.oid;qtyValues[oid]=v;updateSummary();saveStateToURL();if(currentQuoteId)saveQuote(!0);}
function getOptQty(btn){const inp=btn?btn.querySelector('.qty-input'):null;if(inp)return parseFloat(inp.value)||1;const oid=btn?btn.dataset.oid:null;return qtyValues[oid]||1}
function updateCurrency(){updateSummary()}
function updateSummary(){const curr=getCurr();const body=document.getElementById('summaryBody');const hint=document.getElementById('requiredHint');let rows='';let total=0;let allReq=!0;for(const g of templateData){const isReq=g.is_required===1||g.is_required===!0;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';const sel=selections[g.id];const label=GROUP_LABELS[g.name]||g.name_cn||g.name;let gt=0;let has=!1;if(isMulti&&Array.isArray(sel)&&sel.length>0){has=!0;for(const oid of sel){const o=g.options.find(x=>x.id===oid);if(o){const qty=qtyValues[oid]||1;gt+=(o.price_adjustment||0)*qty}}}else if(!isMulti&&sel){has=!0;const o=g.options.find(x=>x.id===sel);if(o){const qty=qtyValues[sel]||1;gt+=(o.price_adjustment||0)*qty}} if(isReq&&!has&&!FOUNDATION_GROUPS.includes(g.name))allReq=!1;if(has){rows+='
';total+=gt}} const hasFoundation=templateData.some(g=>FOUNDATION_GROUPS.includes(g.name)&&((g.selection_type==='multi'||g.selection_type==='multiple')?(selections[g.id]||[]).length>0:!!selections[g.id]));if(!hasFoundation)allReq=!1;document.getElementById('totalPrice').textContent=fmtPrice(total,curr);document.getElementById('basePrice').textContent=fmtPrice(0,curr);body.innerHTML=rows||'
';if(allReq){hint.textContent='✅ 所有必选项已选';hint.className='required-hint show ok'}else{hint.textContent='⚠️ 请完成所有必选项';hint.className='required-hint show'}}
function showQuote(){const curr=getCurr();const sym=CURR_SYMBOL[curr]||'¥';let missing=[];let ok=!0;for(const g of templateData){const isReq=g.is_required===1||g.is_required===!0;if(!isReq||FOUNDATION_GROUPS.includes(g.name))continue;const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';const sel=selections[g.id];const label=GROUP_LABELS[g.name]||g.name_cn||g.name;let has=!1;if(isMulti&&Array.isArray(sel)&&sel.length>0)has=!0;else if(!isMulti&&sel)has=!0;if(!has){ok=!1;missing.push(label);const card=document.querySelector('.card[data-group="'+g.id+'"]');if(card)card.querySelectorAll('.opt-btn').forEach(b=>b.classList.add('required-empty'));}} const hasFoundation=templateData.some(g=>FOUNDATION_GROUPS.includes(g.name)&&((g.selection_type==='multi'||g.selection_type==='multiple')?(selections[g.id]||[]).length>0:!!selections[g.id]));if(!hasFoundation){ok=!1;missing.push('基础结构')} if(!ok){document.getElementById('requiredHint').textContent='⚠️ 必选项缺失: '+missing.join(', ');return} let text='═══ 华营集成房屋 报价单 ═══\n\n';text+='模板: '+templateName+'\n日期: '+new Date().toLocaleDateString('zh-CN')+'\n\n── 配置明细 ──\n';let total=0;for(const g of templateData){const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';const sel=selections[g.id];const label=GROUP_LABELS[g.name]||g.name_cn||g.name;let gt=0;let names=[];if(isMulti&&Array.isArray(sel)){for(const oid of sel){const o=g.options.find(x=>x.id===oid);if(o){const qty=qtyValues[oid]||1;const a=o.price_adjustment||0;names.push((OPTION_LABELS[o.name]||o.name||'')+' ×'+qty+' '+fmtPrice(a*qty,curr));gt+=a*qty}}}else if(!isMulti&&sel){const o=g.options.find(x=>x.id===sel);if(o){const a=o.price_adjustment||0;const qty=qtyValues[sel]||1;names.push((OPTION_LABELS[o.name]||o.name||'')+' ×'+qty+' '+fmtPrice(a*qty,curr));gt+=a*qty}} if(names.length>0){text+=label+': '+names.join(', ')+'\n';total+=gt}} text+='\n── 价格明细 ──\n';text+='选项合计: '+fmtPrice(total,curr)+'\n';text+='─────────────────────────\n💰 总价: '+fmtPrice(total,curr)+'\n\n';text+='条款: FOB 天津 | 货币: '+CURR_LABEL[curr]+'\n有效期: 30天 | 交期: 20-25天\n\n华营集成房屋有限公司\ntrade@mhehy.com';document.getElementById('modalBody').textContent=text;document.getElementById('modal').classList.add('show')} function closeModal(){document.getElementById('modal').classList.remove('show')}
async function exportDOCX(){const{Document,Packer,Paragraph,TextRun,Table,TableRow,TableCell,Header,Footer,AlignmentType,HeadingLevel,BorderStyle,PageNumber,WidthType,ShadingType}=window.docx;const curr=getCurr();
let total=0,rows=[];for(const g of templateData){const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';const sel=selections[g.id];const label=GROUP_LABELS[g.name]||g.name_cn||g.name;if(isMulti&&Array.isArray(sel)) for(const oid of sel){const o=g.options.find(x=>x.id===oid);if(o){const q=qtyValues[oid]||1;rows.push({label,o,qty:q,adj:o.price_adjustment||0});total+=(o.price_adjustment||0)*q}}else if(!isMulti&&sel){const o=g.options.find(x=>x.id===sel);if(o){const q=qtyValues[sel]||1;rows.push({label,o,qty:q,adj:o.price_adjustment||0});total+=(o.price_adjustment||0)*q}}}
const border={style:BorderStyle.SINGLE,size:1,color:"CCCCCC"};const borders={top:border,bottom:border,left:border,right:border};
const headerRow=new TableRow({tableHeader:!0,children:['序号','项目','规格','数量','单价','金额'].map((txt,i)=>{const widths=[600,2200,3200,800,1200,1360];return new TableCell({borders,width:{size:widths[i],type:WidthType.DXA},shading:{fill:"D5E8F0",type:ShadingType.CLEAR},margins:{top:40,bottom:40,left:60,right:60},children:[new Paragraph({alignment:AlignmentType.CENTER,children:[new TextRun({text:txt,bold:!0,size:18,font:"Arial"})]})]})})});
const dataRows=rows.map((r,i)=>{const name=OPTION_LABELS[r.o.name]||r.o.name||'';const unitPrice='¥'+Math.round(r.adj).toLocaleString();const amount='¥'+Math.round(r.adj*r.qty).toLocaleString();const widths=[600,2200,3200,800,1200,1360];return new TableRow({children:[r.label,name,'',''+r.qty,unitPrice,amount].map((txt,j)=>{return new TableCell({borders,width:{size:widths[j],type:WidthType.DXA},margins:{top:30,bottom:30,left:60,right:60},children:[new Paragraph({alignment:j===0?AlignmentType.LEFT:AlignmentType.CENTER,children:[new TextRun({text:''+txt,size:18,font:"Arial"})]})]})})})});
const totalRow=new TableRow({children:[0,1,2,3,4,5].map(j=>{const widths=[600,2200,3200,800,1200,1360];return new TableCell({borders,width:{size:widths[j],type:WidthType.DXA},shading:j===5?{fill:"FFF3E0",type:ShadingType.CLEAR}:j===0?{fill:"F5F5F5",type:ShadingType.CLEAR}:undefined,margins:{top:40,bottom:40,left:60,right:60},children:[new Paragraph({alignment:AlignmentType.CENTER,children:[new TextRun({text:j===0?'合计':j===5?'¥'+Math.round(total).toLocaleString():'',bold:!0,size:20,font:"Arial"})]})]})})});
const doc=new Document({styles:{default:{document:{run:{font:"Arial",size:22}}}},sections:[{properties:{page:{size:{width:12240,height:15840},margin:{top:1200,right:1200,bottom:1200,left:1200}}},headers:{default:new Header({children:[new Paragraph({border:{bottom:{style:BorderStyle.SINGLE,size:6,color:"2E75B6",space:4}},children:[new TextRun({text:"HUAYING Integrated Housing Co., Ltd.",size:16,color:"888888",font:"Arial"})]})]})},footers:{default:new Footer({children:[new Paragraph({alignment:AlignmentType.CENTER,border:{top:{style:BorderStyle.SINGLE,size:4,color:"CCCCCC",space:4}},children:[new TextRun({text:"HUAYING | trade@mhehy.com | Page ",size:16,color:"888888",font:"Arial"}),new TextRun({children:[PageNumber.CURRENT],size:16,color:"888888",font:"Arial"})]})]})},children:[new Paragraph({spacing:{after:80},children:[new TextRun({text:"\u534e\u8425\u96c6\u6210\u623f\u5c4b \u62a5\u4ef7\u5355",bold:!0,size:28,color:"2E75B6",font:"Arial"})]}),new Paragraph({spacing:{after:40},children:[new TextRun({text:"\u6a21\u677f: "+templateName+" | "+new Date().toLocaleDateString('zh-CN'),size:18,color:"666666",font:"Arial"})]}),new Paragraph({spacing:{after:120},children:[new TextRun({text:"\u4e3b\u89c4\u683c: "+currentSize,size:18,color:"666666",font:"Arial"})]}),new Table({width:{size:9360,type:WidthType.DXA},columnWidths:[600,2200,3200,800,1200,1360],rows:[headerRow,...dataRows,totalRow]}),new Paragraph({spacing:{before:200},children:[new TextRun({text:"\u6761\u6b3e: FOB \u5929\u6d25 | \u8d27\u5e01: "+CURR_LABEL[curr],size:18,color:"666666",font:"Arial"})]}),new Paragraph({children:[new TextRun({text:"\u6709\u6548\u671f: 30\u5929 | \u4ea4\u671f: 20-25\u5929",size:18,color:"666666",font:"Arial"})]}),]}]});
const blob=await Packer.toBlob(doc);const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download='HUAYING-\u62a5\u4ef7\u5355-'+new Date().toISOString().slice(0,10)+'.docx';a.click();URL.revokeObjectURL(url)} async function exportPDF(){const{jsPDF}=window.jspdf;const doc=new jsPDF({format:'a4',unit:'mm'});const curr=getCurr();const pw=190,lM=10;let y=15;
function chk(h){if(y+h>282){doc.addPage();y=20}}
let total=0,rows=[],imgList=[];let seq=0;for(const g of templateData){const isMulti=g.selection_type==='multiple'||g.selection_type==='multi';const sel=selections[g.id];const label=GROUP_LABELS[g.name]||g.name_cn||g.name;if(isMulti&&Array.isArray(sel)) for(const oid of sel){const o=g.options.find(x=>x.id===oid);if(o){const q=qtyValues[oid]||1;rows.push({label,o,qty:q,adj:o.price_adjustment||0,seq:seq});if(o.image_url&&o.image_url.length>5)imgList.push({seq,url:o.image_url});seq++;total+=(o.price_adjustment||0)*q}}else if(!isMulti&&sel){const o=g.options.find(x=>x.id===sel);if(o){const q=qtyValues[sel]||1;rows.push({label,o,qty:q,adj:o.price_adjustment||0,seq:seq});if(o.image_url&&o.image_url.length>5)imgList.push({seq,url:o.image_url});seq++;total+=(o.price_adjustment||0)*q}}}
doc.setFont('helvetica','bold');doc.setFontSize(14);doc.text('HUAYING QUOTATION',lM,y);y+=7;doc.setFont('helvetica','normal');doc.setFontSize(9);doc.text('Huaying Integrated Housing Co., Ltd. | trade@mhehy.com',lM,y);y+=4;doc.text('模板: '+templateName+' | '+new Date().toLocaleDateString('zh-CN'),lM,y);y+=4;y+=2;
const cx=[lM,lM+12,lM+95,lM+125,lM+138];const tblEnd=lM+170;function tblLine(yy){doc.setDrawColor(180);doc.line(lM,yy,tblEnd,yy);doc.setDrawColor(0)} tblLine(y);doc.setFontSize(8);doc.setFont('helvetica','bold');doc.text('No.',cx[0]+1,y+3);doc.text('Name / Specification',cx[1]+1,y+3);doc.text('Unit Price',cx[2]+1,y+3);doc.text('Qty',cx[3]+1,y+3);doc.text('Amount',cx[4]+1,y+3);y+=6;tblLine(y);y+=2;
doc.setFont('helvetica','normal');doc.setFontSize(8);let currentLabel='';for(const r of rows){const name=OPTION_LABELS[r.o.name]||r.o.name||'';const unitPrice=fmtPrice(r.adj,curr);const amount=fmtPrice(r.adj*r.qty,curr);const labelTxt=r.label!==currentLabel?r.label:'';
chk(8);if(y>272){tblLine(y);doc.addPage();y=20;tblLine(y);y+=3} tblLine(y);
if(labelTxt){doc.setFont('helvetica','bold');doc.setFontSize(8);doc.text(labelTxt,cx[0]+1,y+2);y+=4;tblLine(y)}
doc.setFont('helvetica','normal');doc.setFontSize(8);const num=(r.seq+1);doc.text(''+num,cx[0]+1,y+2);doc.text(name,cx[1]+1,y+2);doc.text(unitPrice,cx[2]+1,y+2);doc.text(''+r.qty,cx[3]+1,y+2);doc.setFont('helvetica','bold');doc.text(amount,cx[4]+1,y+2);doc.setFont('helvetica','normal');y+=4;currentLabel=r.label} tblLine(y);y+=3;
chk(10);doc.setFontSize(10);doc.setFont('helvetica','bold');doc.text('Total:',cx[1]+1,y+2);doc.setFontSize(12);doc.setTextColor(212,56,13);doc.text(fmtPrice(total,curr),cx[4]+1,y+2);doc.setTextColor(0);y+=7;
doc.setTextColor(120);doc.setFontSize(7);doc.setFont('helvetica','normal');doc.text('FOB Tianjin | Currency: '+CURR_LABEL[curr]+' | Validity: 30 days | Lead Time: 20-25 days',lM,y);
if(imgList.length>0){doc.addPage();y=15;doc.setFont('helvetica','bold');doc.setFontSize(11);doc.setTextColor(0);doc.text('Product Images',lM,y);y+=8;
const loaded=[];for(const imgItem of imgList){try{const img=await new Promise(r=>{const i=new Image();i.crossOrigin='anonymous';i.onload=()=>r(i);i.onerror=()=>r(null);i.src=imgItem.url});if(img){const canvas=document.createElement('canvas');const maxW=60,dpr=4;let w=img.width,h=img.height;if(w>maxW*dpr){h*=maxW*dpr/w;w=maxW*dpr} canvas.width=Math.round(w);canvas.height=Math.round(h);const ctx=canvas.getContext('2d');ctx.imageSmoothingEnabled=!0;ctx.imageSmoothingQuality='high';ctx.drawImage(img,0,0,canvas.width,canvas.height);loaded.push({seq:imgItem.seq,b64:canvas.toDataURL('image/jpeg',0.85),w:Math.round(w/dpr),h:Math.round(h/dpr)})}}catch(e){}}
const cols=3,gap=5,imgW=(pw-(cols-1)*gap)/cols;let col=0,maxH=0;for(const li of loaded){const aspect=li.h/li.w;const renderH=imgW*aspect;const cx=lM+col*(imgW+gap);chk(renderH+8);const num=(li.seq+1);doc.setFontSize(8);doc.setFont('helvetica','bold');doc.text(''+num,cx+imgW/2-2,y+2,{align:'center'});y+=5;doc.addImage(li.b64,'JPEG',cx,y,imgW,renderH);maxH=Math.max(maxH,renderH);col++;if(col>=cols){col=0;y+=maxH+8;maxH=0}} if(col>0)y+=maxH+6}
doc.save('Huaying_Quotation_'+new Date().toISOString().slice(0,10)+'.pdf')} loadTemplate();