|
|
@@ -755,6 +755,8 @@ const RULES=[["目测估算像素/毫米比例","用图纸标注(如有)或
|
|
|
/* rate 默认15 → 单价=所选材质档最高单价(双饰面3500);厨房=宽(最长边)/高(最短边)/总长度/单价 均可调 */
|
|
|
const S={ft:"plan",redbox:false,scale:null,scale600:null,scaleSrc:"",objs:[],mat:0,rate:15,
|
|
|
ruleX:Object.fromEntries(EXTRA.map(e=>[e.id,!e.off])),kitExtras:mkExtras("",true),kitW:null,kitH:null,kitLen:null,kitPrice:null,brand:0,door:2};
|
|
|
+/* 状态兜底:旧版存档/异常恢复可能缺失 objs 等数组字段 → 统一回退空数组,杜绝 undefined.length / undefined.forEach 崩溃 */
|
|
|
+function ensureS(){if(!Array.isArray(S.objs))S.objs=[];if(!Array.isArray(S.redRects))S.redRects=[];if(!Array.isArray(S.redLabels))S.redLabels=[];}
|
|
|
const $=id=>document.getElementById(id);
|
|
|
const fmt=n=>(Math.round(n)||0).toLocaleString("zh-CN");
|
|
|
const fmt2=n=>(Math.round(n*100)/100).toLocaleString("zh-CN",{maximumFractionDigits:2});
|
|
|
@@ -798,7 +800,7 @@ function qNewQuote(){qResetEditor();switchView("editor");qOpenEditorStep(1);}
|
|
|
/* 新建报价单:完整清空上一单状态与识别UI,避免携带旧数据 */
|
|
|
function qResetEditor(){
|
|
|
img=null;overlays=[];ctx.clearRect(0,0,cv.width,cv.height);
|
|
|
- Object.assign(S,{ft:"plan",redbox:false,scale:null,scale600:null,scaleSrc:"",objs:[],mat:0,rate:15,kitW:null,kitH:null,kitLen:null,kitPrice:null,kitBox:null,redArrow:null,brand:0,door:2});
|
|
|
+ Object.assign(S,{ft:"plan",redbox:false,scale:null,scale600:null,scaleSrc:"",objs:[],mat:0,rate:15,kitW:null,kitH:null,kitLen:null,kitPrice:null,kitBox:null,redArrow:null,redRects:[],redLabels:[],brand:0,door:2});
|
|
|
S.ruleX=Object.fromEntries(EXTRA.map(e=>[e.id,!e.off]));S.kitExtras=mkExtras("",true);
|
|
|
$("q-file-unified").value="";$("q-btn-analyze").disabled=true;$("q-step2-next-btn").disabled=true;
|
|
|
$("q-status-drawing").textContent="⬜ 图纸:未上传";$("q-status-drawing").style.background="#EDF2F7";$("q-status-drawing").style.color="var(--text-muted)";
|
|
|
@@ -831,7 +833,7 @@ function draw(){if(!img)return;const k=cv.width/img.naturalWidth;ctx.clearRect(0
|
|
|
ctx.lineWidth=6;ctx.strokeStyle="rgba(255,255,255,.92)";ctx.strokeText(lb,lx,ly);
|
|
|
ctx.fillStyle=o.t==="panel"?"#38A169":"#E53E3E";ctx.fillText(lb,lx,ly);
|
|
|
ctx.textAlign="left";}});}
|
|
|
-function showImg(im){img=im;cv.width=1000;cv.height=Math.round(1000*im.naturalHeight/im.naturalWidth);draw();
|
|
|
+function showImg(im){img=im;overlays=[];cv.width=1000;cv.height=Math.round(1000*im.naturalHeight/im.naturalWidth);draw();
|
|
|
$("q-btn-analyze").disabled=false;
|
|
|
$("q-status-drawing").textContent="✅ 图纸:已上传("+im.naturalWidth+"×"+im.naturalHeight+")";
|
|
|
$("q-status-drawing").style.background="#C6F6D5";$("q-status-drawing").style.color="#276749";}
|
|
|
@@ -865,7 +867,11 @@ function qUseMockDrawing(isU,isCad){const im=genSample(isU,isCad);im.onload=()=>
|
|
|
function qHandleUnified(inp){const f=inp.files[0];if(!f)return;
|
|
|
if(/\.(pdf|dwg|dxf)$/i.test(f.name)){alert("PDF/DWG 请先转 JPG/PNG(Demo 限制)");return;}
|
|
|
const rd=new FileReader();rd.onload=()=>{const im=new Image();im.onload=()=>{showImg(im);qStartAnalysis();};im.src=rd.result;};rd.readAsDataURL(f);}
|
|
|
-function qStartAnalysis(){if(!img){toast("请先上传图纸或选择示例","toast-error");return;}qOpenEditorStep(2);runAI();}
|
|
|
+function qStartAnalysis(){if(!img){toast("请先上传图纸或选择示例","toast-error");return;}qOpenEditorStep(2);
|
|
|
+ runAI().catch(e=>{ /* 识别异常兜底:清叠加、重绘画布、提示重试,避免留下错位/半残页面 */
|
|
|
+ overlays=[];if(img)draw();setProg(0,"");setProg(1,"");setProg(2,"");
|
|
|
+ logAI("⚠ AI 识别引擎异常:"+(e&&e.message?e.message:e)+"。画布已重置,请重试或更换图纸。","e");
|
|
|
+ toast("AI 识别异常,请重试","toast-error");});}
|
|
|
/* ========== AI 识别引擎 v3(墙体分离 + 柜体特征分类) ========== */
|
|
|
function logAI(html,cls){const el=$("q-ai-log");const s=document.createElement("span");if(cls)s.className=cls;s.innerHTML=html;el.appendChild(s);el.scrollTop=el.scrollHeight;}
|
|
|
function setProg(i,st){$("q-ai-prog-"+i).className="ai-progress-step "+(st||"");}
|
|
|
@@ -977,6 +983,7 @@ function wallThickness(wm,w,h){const runs=[];
|
|
|
if(!runs.length)return 6;runs.sort((a,b)=>a-b);return runs[runs.length>>1];}
|
|
|
async function runAI(){
|
|
|
$("q-ai-log").innerHTML="";overlays=[];S.redbox=false;S.scale=null;S.scale600=null;S.scaleSrc="";S.objs=[];S.ft="plan";S.redRects=[];S.redLabels=[];S.kitBox=null;S.redArrow=null;S.kitW=null;S.kitH=null;S.kitLen=null;S.kitPrice=null;
|
|
|
+ if(img)draw(); /* 重置后立即重绘干净画布:避免新图纸复用上一张图的叠加红框,出现“错位页面” */
|
|
|
$("q-ai-results").style.display="none";
|
|
|
$("q-red-anno-row").style.display="none";
|
|
|
$("q-step2-next-btn").disabled=true;
|
|
|
@@ -1426,10 +1433,10 @@ async function runAI(){
|
|
|
$("q-step2-next-btn").disabled=false;
|
|
|
}
|
|
|
function fh(){return parseFloat($("q-floor-height").value)||2.5;}
|
|
|
-function qFhChanged(src){const v=(src?src.value:$("q-floor-height").value)||"2.5";
|
|
|
+function qFhChanged(src){ensureS();const v=(src?src.value:$("q-floor-height").value)||"2.5";
|
|
|
$("q-floor-height").value=v;const i2=$("q-floor-height2");if(i2)i2.value=v;
|
|
|
if(S.objs.length||S.kitBox){renderAIResults();recalc();}}
|
|
|
-function renderAIResults(){
|
|
|
+function renderAIResults(){ensureS();
|
|
|
const sc=dimScale();
|
|
|
$("q-red-anno-row").style.display=S.redArrow?"block":"none";
|
|
|
renderAnnoInfer();
|
|
|
@@ -1454,7 +1461,7 @@ function renderAIResults(){
|
|
|
|
|
|
/* ========== 对象表(步骤3) ========== */
|
|
|
let KIT_TR=null,TOTAL_TR=null;
|
|
|
-function rebuildObjs(){
|
|
|
+function rebuildObjs(){ensureS();
|
|
|
const tb=$("objBody");tb.innerHTML="";KIT_TR=null;TOTAL_TR=null;
|
|
|
/* 厨房行:与柜体合并同表;宽=最长边、高=最短边、总长度(L/U型自动算,可改)、单价(元/m,默认材质档最高,可改) */
|
|
|
if(S.kitW>0&&S.kitH>0){const tr=document.createElement("tr");KIT_TR=tr;
|
|
|
@@ -1569,7 +1576,7 @@ function calcExtras(p,isKit){
|
|
|
|
|
|
/* ========== 计算引擎 ========== */
|
|
|
let LASTQ={baseSum:0,kSum:0,wSum:0,varArea:0,cabArea:0,cabAmt:0,baseRows:[],valueRows:[]};
|
|
|
-function recalc(){
|
|
|
+function recalc(){ensureS();
|
|
|
const pr=precision(),sc=dimScale();
|
|
|
$("precBadge").className="badge "+pr.cls;$("precBadge").textContent=pr.txt;
|
|
|
$("q-quote-prec").className="badge "+pr.cls;$("q-quote-prec").textContent=pr.txt;
|
|
|
@@ -1652,7 +1659,7 @@ let QUOTES=[];
|
|
|
try{QUOTES=JSON.parse(localStorage.getItem("aiq_new_quotes")||"[]");}catch(e){QUOTES=[];}
|
|
|
function saveQuotes(){try{localStorage.setItem("aiq_new_quotes",JSON.stringify(QUOTES));}catch(e){}}
|
|
|
function snapshot(){return JSON.stringify({ft:S.ft,redbox:S.redbox,scale:S.scale,scaleSrc:S.scaleSrc,mat:S.mat,rate:S.rate,kitW:S.kitW,kitH:S.kitH,kitLen:S.kitLen,kitPrice:S.kitPrice,kitEx:S.kitExtras,ruleX:S.ruleX,brand:S.brand,door:S.door,objs:S.objs.map(o=>({name:o.name,kind:o.kind,src:o.src,w:o.w,h:o.h,lenM:o.lenM!=null?o.lenM:null,hgtM:o.hgtM!=null?o.hgtM:null,price:o.price!=null?o.price:null,ex:o.extras||{}}))});}
|
|
|
-function qSaveQuote(silent){
|
|
|
+function qSaveQuote(silent){ensureS();
|
|
|
if(!S.objs.length&&!LASTQ.baseSum&&!LASTQ.wSum){if(!silent)toast("暂无计价内容,请先上传图纸完成AI识别","toast-error");return;}
|
|
|
const m=MATS[S.mat],fixed=LASTQ.wSum;
|
|
|
const q={id:Date.now(),name:("报价单 "+(QUOTES.length+1)),date:new Date().toLocaleString("zh-CN"),mat:m.n,rate:S.rate,
|
|
|
@@ -1679,6 +1686,7 @@ function renderQuoteList(){
|
|
|
function qOpenQuote(q){
|
|
|
try{const snap=JSON.parse(q.snap);
|
|
|
Object.assign(S,{ft:snap.ft,redbox:snap.redbox,scale:snap.scale,scaleSrc:snap.scaleSrc,mat:snap.mat,rate:snap.rate,kitW:snap.kitW!=null?snap.kitW:null,kitH:snap.kitH!=null?snap.kitH:null,kitLen:snap.kitLen!=null?snap.kitLen:null,kitPrice:snap.kitPrice!=null?snap.kitPrice:null,objs:snap.objs});
|
|
|
+ ensureS(); /* 旧版存档可能缺 objs 等字段 → 回退空数组,避免恢复后 undefined 崩溃 */
|
|
|
S.kitExtras=snap.kitEx||mkExtras("",true);
|
|
|
S.ruleX=Object.assign(Object.fromEntries(EXTRA.map(e=>[e.id,!e.off])),snap.ruleX||{});
|
|
|
S.brand=snap.brand!=null?snap.brand:0;
|