217 lines
10 KiB
JavaScript
217 lines
10 KiB
JavaScript
/* 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');}
|
||
}
|