Files
lol-skin/.data/www/网页/js/career.js
T

217 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* career.js — 个人生涯,完全对照参考图 */
let _cPuuid=null,_cGames=[],_cParsed=[],_cSummoner=null;
const QMAP={420:'单排',440:'灵活组排',430:'普通对战',450:'极地大乱斗',
900:'无限火力',1700:'斗魂竞技场',1090:'云顶之弈',1100:'云顶排位',
3140:'多人 训练模式 自定义',0:'自定义'};
const qname=id=>QMAP[id]||`模式${id}`;
const tierZh=t=>({'IRON':'黑铁','BRONZE':'黄铜','SILVER':'白银','GOLD':'黄金',
'PLATINUM':'铂金','EMERALD':'翡翠','DIAMOND':'钻石','MASTER':'大师',
'GRANDMASTER':'宗师','CHALLENGER':'王者'}[t?.toUpperCase()]||t||'--');
const ip=p=>p?`/api/img?path=${encodeURIComponent(p)}`:'';
async function renderCareer(){
const page=document.getElementById('page-career');
page.innerHTML='<div class="loading-spin">加载生涯</div>';
try{
const s=await API.get('summoner');
_cSummoner=s;_cPuuid=s.puuid;
await _loadCareer(s.puuid,s);
}catch(e){page.innerHTML='<div class="placeholder-page"><p>⏳ 请先启动 LOL 客户端</p></div>';}
}
async function _loadCareer(puuid,summoner){
const page=document.getElementById('page-career');
page.innerHTML='<div class="loading-spin">加载生涯数据</div>';
try{
const[rr,gr]=await Promise.allSettled([
API.get('ranked'),
API.get('games',{puuid,beg:0,end:19})
]);
const ranked=rr.status==='fulfilled'?rr.value:null;
const raw=gr.status==='fulfilled'?gr.value:null;
console.log('[career] raw keys=', raw ? Object.keys(raw) : null);
console.log('[career] raw.games=', raw?.games ? Object.keys(raw.games) : null);
console.log('[career] gameCount=', raw?.games?.gameCount);
const list=raw?.games?.games||raw?.games||[];
_cGames=Array.isArray(list)?list:[];
_cParsed=_cGames.map(g=>_parseGame(g,puuid));
console.log('[career] _cGames.length=', _cGames.length, '_cParsed=', _cParsed.length);
if(_cParsed.length) console.log('[career] first game=', _cParsed[0]);
_buildPage(summoner,ranked,_cParsed);
}catch(e){page.innerHTML='<div class="placeholder-page"><p>加载失败</p></div>';}
}
function _parseGame(game,puuid){
const ids=game.participantIdentities||[];
const ps=game.participants||[];
let pid=-1;
for(const id of ids){if(id.player?.puuid===puuid){pid=id.participantId;break;}}
if(pid===-1&&ids.length)pid=ids[0].participantId;
const p=ps.find(x=>x.participantId===pid)||ps[0]||{};
const st=p.stats||{};
const cid=p.championId||0;
const secs=game.gameDuration||0;
const dur=`${String(Math.floor(secs/60)).padStart(2,'0')}:${String(secs%60).padStart(2,'0')}`;
const ts=game.gameCreation||0;
let t='';
if(ts){const d=new Date(ts);t=`${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')} · ${d.getFullYear()}/${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getDate()).padStart(2,'0')}`;}
return{
queueId:game.queueId||0,win:st.win||false,remake:st.gameEndedInEarlySurrender||false,
kills:st.kills||0,deaths:st.deaths||0,assists:st.assists||0,
champLevel:st.champLevel||1,cs:(st.totalMinionsKilled||0)+(st.neutralMinionsKilled||0),
gold:st.goldEarned||0,champIcon:`/lol-game-data/assets/v1/champion-icons/${cid}.png`,
sp1:`/lol-game-data/assets/v1/summoner-spell-icons/${p.spell1Id||0}.png`,
sp2:`/lol-game-data/assets/v1/summoner-spell-icons/${p.spell2Id||0}.png`,
items:[st.item0,st.item1,st.item2,st.item3,st.item4,st.item5,st.item6]
.map(id=>id?`/lol-game-data/assets/v1/item-icons/${id}.png`:''),
time:t,dur,
};
}
function _stats(games){
let k=0,d=0,a=0,w=0,l=0;
for(const g of games){
if(g.remake||g.queueId===0)continue;
k+=g.kills;d+=g.deaths;a+=g.assists;
if(g.win)w++;else l++;
}
return{k,d,a,w,l,kda:d===0?(k+a).toFixed(1):((k+a)/d).toFixed(1)};
}
function _champIcons(games){
const map={};
for(const g of games){
if(!g.champIcon)continue;
if(!map[g.champIcon])map[g.champIcon]={icon:g.champIcon,t:0,w:0};
map[g.champIcon].t++;if(g.win)map[g.champIcon].w++;
}
return Object.values(map).sort((a,b)=>b.t-a.t).slice(0,10);
}
function _rankRow(r,label){
const t=r.tier||'',dv=r.division||'';
const w=r.wins||0,l=r.losses||0,tot=w+l;
const div=dv==='NA'?'':dv;
const tier=t?tierZh(t)+' '+div:'--';
const high=r.highestTier?tierZh(r.highestTier)+' '+(r.highestDivision==='NA'?'':r.highestDivision):'--';
const prev=r.previousSeasonEndTier?tierZh(r.previousSeasonEndTier)+' '+(r.previousSeasonEndDivision==='NA'?'':r.previousSeasonEndDivision):'--';
return`<tr><td>${label}</td><td>${tot||0}</td><td>${tot?Math.round(w*100/tot)+'%':'--'}</td><td class="cw">${w}</td><td class="cl">${l}</td><td>${tier}</td><td>${r.leaguePoints??0}</td><td>${high}</td><td>${prev}</td></tr>`;
}
function _buildPage(s,ranked,games){
const page=document.getElementById('page-career');
const name=s.gameName||s.displayName||'---';
const tag=s.tagLine||'';
const lvl=s.summonerLevel||'--';
const iconUrl=ip(`/lol-game-data/assets/v1/profile-icons/${s.profileIconId||29}.jpg`);
const copyTxt=tag?`${name}#${tag}`:name;
const solo=ranked?.queueMap?.['RANKED_SOLO_5x5']||{};
const flex=ranked?.queueMap?.['RANKED_FLEX_SR']||{};
const{k,d,a,w,l,kda}=_stats(games);
const champs=_champIcons(games).map(c=>`<img class="cv-cm" src="${ip(c.icon)}" onerror="this.style.display='none'">`).join('');
page.innerHTML=`
<div class="cv-page">
<div class="cv-header">
<div class="cv-center">
<div class="cv-ring-wrap">
<svg class="cv-ring-svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="44" fill="none" stroke="#e8eaed" stroke-width="5"/>
<circle cx="50" cy="50" r="44" fill="none" stroke="#1677ff" stroke-width="5"
stroke-dasharray="276" stroke-dashoffset="60" stroke-linecap="round"
transform="rotate(-90 50 50)"/>
</svg>
<img class="cv-avatar" src="${iconUrl}" onerror="this.onerror=null;this.style.background='#3a4a6a'">
<div class="cv-lvl">${lvl}</div>
</div>
<div class="cv-info">
<div class="cv-namerow">
<span class="cv-name">${esc(name)}</span>
<button class="cv-cpbtn" onclick="navigator.clipboard?.writeText('${esc(copyTxt)}');showToast('已复制','ok')" title="复制 ${esc(copyTxt)}">
<svg viewBox="0 0 24 24" width="13" height="13"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
</button>
</div>
${tag?`<div class="cv-tag"># ${esc(tag)}</div>`:''}
</div>
</div>
<div class="cv-btns">
<button class="cv-btn" onclick="careerBackToMe()">回到自己</button>
<button class="cv-btn" onclick="careerRefresh()">刷新</button>
</div>
</div>
<div class="cv-tbl-wrap">
<table class="cv-tbl">
<thead><tr><th>游戏类型</th><th>总计</th><th>胜率</th><th>胜场</th><th>败场</th><th>段位</th><th>胜点</th><th>最高段位</th><th>上赛季结算段位</th></tr></thead>
<tbody>${_rankRow(solo,'单排')}${_rankRow(flex,'灵活组排')}</tbody>
</table>
</div>
<div class="cv-sbar">
<span class="cv-slbl">近期战绩(最近 ${games.length} 场)</span>
<span class="cw" style="font-weight:600">胜场:${w}</span>
<span class="cl" style="font-weight:600">败场:${l}</span>
<span class="cv-ssub">KDA: ${k} / ${d} / ${a} (${kda})</span>
<div class="cv-cicons">${champs}</div>
<div style="flex:1"></div>
<select class="cv-sel" onchange="careerFilter(+this.value)">
<option value="0">全部</option>
<option value="430">普通对战</option>
<option value="450">大乱斗</option>
<option value="420">单排</option>
<option value="440">组排</option>
</select>
</div>
<div class="cv-glist" id="cvGL"></div>
</div>`;
_renderGames(games,0);
}
function _renderGames(games,qid){
const el=document.getElementById('cvGL');if(!el)return;
const list=qid?games.filter(g=>g.queueId===qid):games;
if(!list.length){el.innerHTML='<div class="placeholder-page" style="height:200px"><p>暂无战绩</p></div>';return;}
el.innerHTML=list.map(g=>{
const win=g.win,rm=g.remake;
const rc=rm?'cr':'';
const bc=rm?'gbar re':(win?'gbar win':'gbar lose');
const rtxt=rm?'重开':(win?'胜利':'失败');
const rcls=rm?'gres cr':(win?'gres cw':'gres cl');
const gold=g.gold>=1000?(g.gold/1000).toFixed(1)+'k':g.gold||0;
const items=(g.items||[]).slice(0,7).map(p=>
p?`<img class="gi" src="${ip(p)}" onerror="this.style.opacity=0">`
:`<span class="gi-e"></span>`).join('');
const s1=g.sp1?`<img class="gsp" src="${ip(g.sp1)}" onerror="this.style.opacity=0">`:'';
const s2=g.sp2?`<img class="gsp" src="${ip(g.sp2)}" onerror="this.style.opacity=0">`:'';
return`<div class="${bc}">
<div class="gbar-l">
<div class="${rcls}">${rtxt}</div>
<div class="gqn">${esc(qname(g.queueId))}</div>
</div>
<div class="gbar-ch">
<div class="gch-wrap">
<img class="gch" src="${ip(g.champIcon)}" onerror="this.onerror=null;this.style.background='#2a3a5c'">
<span class="gchl">${g.champLevel}</span>
</div>
<div class="gsps">${s1}${s2}</div>
</div>
<div class="gbar-kda">
<div class="gkda">${g.kills} / <span class="cl">${g.deaths}</span> / ${g.assists}</div>
<div class="gcs">${g.cs} CS · ${g.dur}</div>
</div>
<div class="gbar-items">${items}</div>
<div class="gbar-gold"><span class="ggold">${gold}</span> <svg viewBox="0 0 16 16" width="14" height="14"><circle cx="8" cy="8" r="7" fill="#c0c0c0"/><text x="8" y="11.5" text-anchor="middle" font-size="9" fill="#fff" font-weight="700">G</text></svg></div>
<div class="gbar-time">${esc(g.time||'')}</div>
</div>`;
}).join('');
}
function careerFilter(qid){_renderGames(_cParsed,qid);}
async function careerRefresh(){if(_cPuuid&&_cSummoner)await _loadCareer(_cPuuid,_cSummoner);}
async function careerBackToMe(){
try{const s=await API.get('summoner');_cSummoner=s;_cPuuid=s.puuid;await _loadCareer(s.puuid,s);}
catch(e){showToast('获取失败','err');}
}