fix: composite dashboard RPM gauge - ╭ shape (left 90° arc + top straight line)

This commit is contained in:
2026-07-25 16:48:05 +08:00
parent 77815f36a0
commit 67ac9a57fe
+55 -25
View File
@@ -48,7 +48,7 @@ html,body{width:100%;height:100%;overflow:hidden;background:#0a0a0f;color:#fff;
</head> </head>
<body> <body>
<div id="root"><div id="box" class="contain"><div id="content"> <div id="root"><div id="box" class="contain"><div id="content">
<div id="rpm-area"><svg id="rpm-svg" viewBox="0 0 640 440" preserveAspectRatio="xMidYMid meet"></svg></div> <div id="rpm-area"><svg id="rpm-svg" viewBox="0 0 640 480" preserveAspectRatio="xMidYMid meet"></svg></div>
<div id="speed-area"><div id="speed-val">0</div><div id="speed-unit">KM/H</div></div> <div id="speed-area"><div id="speed-val">0</div><div id="speed-unit">KM/H</div></div>
<div id="gear-area"><div id="gear-box"><span id="gear-val">1</span></div></div> <div id="gear-area"><div id="gear-box"><span id="gear-val">1</span></div></div>
<div id="pedals"> <div id="pedals">
@@ -74,10 +74,62 @@ let GAME_ID='';
const proto=location.protocol==='https:'?'wss:':'ws:',wsUrl=proto+'//'+location.host+'/ws'; const proto=location.protocol==='https:'?'wss:':'ws:',wsUrl=proto+'//'+location.host+'/ws';
let ws,rt,lR=-1,lG=-999; let ws,rt,lR=-1,lG=-999;
const CX=320,CY=380,R=290,SA=-210,EA=-30; const CX=300,CY=420,R=300,SA=180,EA=270;
function d2r(d){return d*Math.PI/180} function d2r(d){return d*Math.PI/180}
function arcP(s,e,r){const sa=d2r(s),ea=d2r(e);return`M${CX+r*Math.cos(sa)} ${CY+r*Math.sin(sa)} A${r} ${r} 0 ${e-s>180?1:0} 1 ${CX+r*Math.cos(ea)} ${CY+r*Math.sin(ea)}`}
function drawRpm(cur,max,gear){
max=max||8000;cur=cur||0;
const sx=CX+R*Math.cos(d2r(SA)),sy=CY+R*Math.sin(d2r(SA)); // arc start
const ex=CX+R*Math.cos(d2r(EA)),ey=CY+R*Math.sin(d2r(EA)); // arc end -> top of arc
const lx=620,ly=ey; // straight line end (right side, same Y as arc top)
// arc length (90°) = 2πR * 90/360
const arcLen=Math.PI*R*90/360;
const lineLen=lx-ex;
const totalLen=arcLen+lineLen;
let h='';
// background: arc + line
h+=`<path d="M${sx},${sy} A${R},${R} 0 0 0 ${ex},${ey} L${lx},${ly}" fill="none" stroke="rgba(255,255,255,.04)" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>`;
// scale marks on arc
for(let r=0;r<=max;r+=1000){
const pct=r/max;
const ang=SA+(EA-SA)*pct;
if(ang<=EA){
const a=d2r(ang);
const x1=CX+(R-18)*Math.cos(a),y1=CY+(R-18)*Math.sin(a);
const x2=CX+(R+18)*Math.cos(a),y2=CY+(R+18)*Math.sin(a);
const c=r>=max*0.85?'rgba(255,255,255,.12)':(r%2000===0?'rgba(255,255,255,.15)':'rgba(255,255,255,.05)');
h+=`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${c}" stroke-width="${r%2000===0?2:1}"/>`;
if(r%2000===0){
h+=`<text x="${CX+(R-34)*Math.cos(a)}" y="${CY+(R-34)*Math.sin(a)+4}" text-anchor="middle" font-size="10" fill="rgba(255,255,255,.2)">${r/1000}</text>`;
}
}
}
// progress
if(cur>0){
const pct=Math.min(cur/max,1);
if(pct<=arcLen/totalLen){
// progress within arc
const ap=pct*totalLen/arcLen;
const ang=SA+(EA-SA)*ap;
const px=CX+R*Math.cos(d2r(ang)),py=CY+R*Math.sin(d2r(ang));
h+=`<path d="M${sx},${sy} A${R},${R} 0 0 0 ${px},${py}" fill="none" stroke="#fff" stroke-width="22" stroke-linecap="round"/>`;
}else{
// full arc + partial line
const lp=(pct*totalLen-arcLen)/lineLen;
const px=ex+lp*(lx-ex);
h+=`<path d="M${sx},${sy} A${R},${R} 0 0 0 ${ex},${ey} L${px},${ly}" fill="none" stroke="#fff" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>`;
}
}
h+=`<text x="${CX}" y="${CY-24}" text-anchor="middle" font-size="22" font-weight="200" fill="#fff">${Math.round(cur).toLocaleString()}</text>`;
h+=`<text x="${CX}" y="${CY+4}" text-anchor="middle" font-size="10" fill="rgba(255,255,255,.15)">&times;1000 rpm</text>`;
svg.innerHTML=h;
}
function parseRatio(r){if(!r||r==='auto')return null;const p=r.split(':');if(p.length===2){const w=+p[0],h=+p[1];return w/h}return null} function parseRatio(r){if(!r||r==='auto')return null;const p=r.split(':');if(p.length===2){const w=+p[0],h=+p[1];return w/h}return null}
@@ -90,28 +142,6 @@ let GAME_ID='';
box.style.width=cw+'px';box.style.height=ch+'px' box.style.width=cw+'px';box.style.height=ch+'px'
} }
function drawRpm(cur,max,gear){
max=max||8000;cur=cur||0;
const ta=EA-SA,rd=max*0.85;
let h='';
h+=`<path d="${arcP(SA,EA,R)}" fill="none" stroke="rgba(255,255,255,.04)" stroke-width="22" stroke-linecap="round"/>`;
for(let r=0;r<=max;r+=1000){
const pct=r/max,ang=SA+ta*pct,a=d2r(ang);
const x1=CX+(R-20)*Math.cos(a),y1=CY+(R-20)*Math.sin(a);
const x2=CX+(R+20)*Math.cos(a),y2=CY+(R+20)*Math.sin(a);
const c=r>=rd?'rgba(255,255,255,.08)':(r%2000===0?'rgba(255,255,255,.12)':'rgba(255,255,255,.04)');
h+=`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${c}" stroke-width="${r%2000===0?2:1}"/>`;
if(r%2000===0)h+=`<text x="${CX+(R-36)*Math.cos(a)}" y="${CY+(R-36)*Math.sin(a)+4}" text-anchor="middle" font-size="10" fill="rgba(255,255,255,.2)">${r/1000}</text>`;
}
if(cur>0){
const pct=Math.min(cur/max,1),eA=SA+ta*pct;
h+=`<path d="${arcP(SA,eA,R)}" fill="none" stroke="#fff" stroke-width="22" stroke-linecap="round"/>`;
}
h+=`<text x="${CX}" y="${CY-12}" text-anchor="middle" font-size="24" font-weight="200" fill="#fff">${Math.round(cur).toLocaleString()}</text>`;
h+=`<text x="${CX}" y="${CY+14}" text-anchor="middle" font-size="11" fill="rgba(255,255,255,.2)">&times;1000 rpm</text>`;
svg.innerHTML=h;
}
function connect(){ function connect(){
ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}} ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}}
ws.onmessage=(e)=>{try{const m=JSON.parse(e.data); ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);