2.1 : New
This commit is contained in:
@@ -217,20 +217,21 @@
|
||||
{{ detail_item('Assists (场均助攻)', features['basic_avg_assisted_kill'], 'basic_avg_assisted_kill') }}
|
||||
{{ detail_item('AWP Kills (狙击击杀)', features['basic_avg_awp_kill'], 'basic_avg_awp_kill') }}
|
||||
{{ detail_item('Jumps (场均跳跃)', features['basic_avg_jump_count'], 'basic_avg_jump_count', '{:.1f}') }}
|
||||
{{ detail_item('Knife Kills (场均刀杀)', features['basic_avg_knife_kill'], 'basic_avg_knife_kill') }}
|
||||
{{ detail_item('Zeus Kills (电击枪杀)', features['basic_avg_zeus_kill'], 'basic_avg_zeus_kill') }}
|
||||
{{ detail_item('Zeus Buy% (起电击枪)', features['basic_zeus_pick_rate'], 'basic_zeus_pick_rate', '{:.1%}') }}
|
||||
|
||||
<!-- Row 3: Objective -->
|
||||
{{ detail_item('MVP (最有价值)', features['basic_avg_mvps'], 'basic_avg_mvps') }}
|
||||
{{ detail_item('Plants (下包)', features['basic_avg_plants'], 'basic_avg_plants') }}
|
||||
{{ detail_item('Defuses (拆包)', features['basic_avg_defuses'], 'basic_avg_defuses') }}
|
||||
{{ detail_item('Flash Assist (闪光助攻)', features['basic_avg_flash_assists'], 'basic_avg_flash_assists') }}
|
||||
<div class="hidden lg:block"></div> <!-- Spacer -->
|
||||
|
||||
<!-- Row 4: Opening -->
|
||||
{{ detail_item('First Kill (场均首杀)', features['basic_avg_first_kill'], 'basic_avg_first_kill') }}
|
||||
{{ detail_item('First Death (场均首死)', features['basic_avg_first_death'], 'basic_avg_first_death') }}
|
||||
{{ detail_item('FK Rate (首杀率)', features['basic_first_kill_rate'], 'basic_first_kill_rate', '{:.1%}') }}
|
||||
{{ detail_item('FD Rate (首死率)', features['basic_first_death_rate'], 'basic_first_death_rate', '{:.1%}') }}
|
||||
<div class="hidden lg:block"></div> <!-- Spacer -->
|
||||
|
||||
<!-- Row 5: Multi-Kills -->
|
||||
{{ detail_item('2K Rounds (双杀)', features['basic_avg_kill_2'], 'basic_avg_kill_2') }}
|
||||
@@ -321,6 +322,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-xs font-black text-gray-400 uppercase tracking-widest mb-4 border-b border-gray-100 dark:border-slate-700 pb-2">
|
||||
ROUND (Round Dynamics)
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-y-6 gap-x-4">
|
||||
{{ detail_item('Kill Early (前30秒击杀)', features['rd_phase_kill_early_share'], 'rd_phase_kill_early_share', '{:.1%}') }}
|
||||
{{ detail_item('Kill Mid (30-60秒击杀)', features['rd_phase_kill_mid_share'], 'rd_phase_kill_mid_share', '{:.1%}') }}
|
||||
{{ detail_item('Kill Late (60秒后击杀)', features['rd_phase_kill_late_share'], 'rd_phase_kill_late_share', '{:.1%}') }}
|
||||
{{ detail_item('Death Early (前30秒死亡)', features['rd_phase_death_early_share'], 'rd_phase_death_early_share', '{:.1%}') }}
|
||||
{{ detail_item('Death Mid (30-60秒死亡)', features['rd_phase_death_mid_share'], 'rd_phase_death_mid_share', '{:.1%}') }}
|
||||
{{ detail_item('Death Late (60秒后死亡)', features['rd_phase_death_late_share'], 'rd_phase_death_late_share', '{:.1%}') }}
|
||||
|
||||
{{ detail_item('FirstDeath Win% (首死后胜率)', features['rd_firstdeath_team_first_death_win_rate'], 'rd_firstdeath_team_first_death_win_rate', '{:.1%}', count_label=features['rd_firstdeath_team_first_death_rounds']) }}
|
||||
{{ detail_item('Invalid Death% (无效死亡)', features['rd_invalid_death_rate'], 'rd_invalid_death_rate', '{:.1%}', count_label=features['rd_invalid_death_rounds']) }}
|
||||
{{ detail_item('Pressure KPR (落后≥3)', features['rd_pressure_kpr_ratio'], 'rd_pressure_kpr_ratio', '{:.2f}x') }}
|
||||
{{ detail_item('MatchPt KPR (赛点放大)', features['rd_matchpoint_kpr_ratio'], 'rd_matchpoint_kpr_ratio', '{:.2f}x', count_label=features['rd_matchpoint_rounds']) }}
|
||||
{{ detail_item('Trade Resp (10s响应)', features['rd_trade_response_10s_rate'], 'rd_trade_response_10s_rate', '{:.1%}') }}
|
||||
|
||||
{{ detail_item('Pressure Perf (Leetify)', features['rd_pressure_perf_ratio'], 'rd_pressure_perf_ratio', '{:.2f}x') }}
|
||||
{{ detail_item('MatchPt Perf (Leetify)', features['rd_matchpoint_perf_ratio'], 'rd_matchpoint_perf_ratio', '{:.2f}x') }}
|
||||
{{ detail_item('Comeback KillShare (追分)', features['rd_comeback_kill_share'], 'rd_comeback_kill_share', '{:.1%}', count_label=features['rd_comeback_rounds']) }}
|
||||
{{ detail_item('Map Stability (地图稳定)', features['map_stability_coef'], 'map_stability_coef', '{:.3f}') }}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
<div class="bg-gray-50 dark:bg-slate-700/30 rounded-xl p-4 border border-gray-100 dark:border-slate-600">
|
||||
<div class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Phase Split</div>
|
||||
<div class="h-40">
|
||||
<canvas id="phaseChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-slate-700/30 rounded-xl p-4 border border-gray-100 dark:border-slate-600">
|
||||
<div class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Top Weapons</div>
|
||||
<div id="weaponTopTable" class="text-sm"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-slate-700/30 rounded-xl p-4 border border-gray-100 dark:border-slate-600">
|
||||
<div class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Round Type Split</div>
|
||||
<div class="text-[11px] text-gray-500 dark:text-gray-400 mb-2">
|
||||
KPR=Kills per Round(每回合击杀) · Perf=Leetify Round Performance Score(回合表现分)
|
||||
</div>
|
||||
<div id="roundTypeTable" class="text-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group 5: SPECIAL (Clutch & Multi) -->
|
||||
<div>
|
||||
<h4 class="text-xs font-black text-gray-400 uppercase tracking-widest mb-4 border-b border-gray-100 dark:border-slate-700 pb-2">
|
||||
@@ -951,7 +997,176 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const phaseCanvas = document.getElementById('phaseChart');
|
||||
if (phaseCanvas) {
|
||||
const ctxPhase = phaseCanvas.getContext('2d');
|
||||
new Chart(ctxPhase, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Early', 'Mid', 'Late'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Kills',
|
||||
data: [
|
||||
{{ features.get('rd_phase_kill_early_share', 0) }},
|
||||
{{ features.get('rd_phase_kill_mid_share', 0) }},
|
||||
{{ features.get('rd_phase_kill_late_share', 0) }}
|
||||
],
|
||||
backgroundColor: 'rgba(124, 58, 237, 0.55)'
|
||||
},
|
||||
{
|
||||
label: 'Deaths',
|
||||
data: [
|
||||
{{ features.get('rd_phase_death_early_share', 0) }},
|
||||
{{ features.get('rd_phase_death_mid_share', 0) }},
|
||||
{{ features.get('rd_phase_death_late_share', 0) }}
|
||||
],
|
||||
backgroundColor: 'rgba(148, 163, 184, 0.55)'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
suggestedMax: 1,
|
||||
ticks: {
|
||||
callback: (v) => `${Math.round(v * 100)}%`
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: true, position: 'bottom' },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (ctx) => `${ctx.dataset.label}: ${(ctx.parsed.y * 100).toFixed(1)}%`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const weaponTop = JSON.parse({{ (features.get('rd_weapon_top_json', '[]') or '[]') | tojson }});
|
||||
const weaponTopEl = document.getElementById('weaponTopTable');
|
||||
if (weaponTopEl) {
|
||||
if (!Array.isArray(weaponTop) || weaponTop.length === 0) {
|
||||
weaponTopEl.innerHTML = '<div class="text-gray-500 dark:text-gray-400">No data</div>';
|
||||
} else {
|
||||
const matchesPlayed = Number({{ features.get('total_matches', 0) or 0 }}) || 0;
|
||||
const weaponRankMap = {{ (distribution.get('top_weapon_rank_map', {}) or {}) | tojson }};
|
||||
const rows = weaponTop.map(w => {
|
||||
const kills = Number(w.kills || 0);
|
||||
const hsRate = Number(w.hs_rate || 0);
|
||||
const kpm = matchesPlayed > 0 ? (kills / matchesPlayed) : kills;
|
||||
return { ...w, kills, hsRate, kpm };
|
||||
});
|
||||
|
||||
rows.sort((a, b) => b.kpm - a.kpm);
|
||||
|
||||
const catMap = { pistol: '副武器', smg: '冲锋枪', shotgun: '霰弹枪', rifle: '步枪', sniper: '狙击枪', lmg: '重机枪' };
|
||||
const fmtPct = (v) => `${(v * 100).toFixed(1)}%`;
|
||||
|
||||
weaponTopEl.innerHTML = `
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="text-gray-500 dark:text-gray-400">
|
||||
<tr>
|
||||
<th class="text-left font-bold py-1 pr-2">武器</th>
|
||||
<th class="text-right font-bold py-1 px-2">击杀</th>
|
||||
<th class="text-right font-bold py-1 px-2">爆头率</th>
|
||||
<th class="text-left font-bold py-1 pl-2">价格/类型</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-gray-700 dark:text-gray-200">
|
||||
${rows.map((w) => {
|
||||
const category = catMap[w.category] || (w.category || '');
|
||||
const price = (w.price != null) ? `$${w.price}` : '—';
|
||||
const info = weaponRankMap[w.weapon] || {};
|
||||
const kpmRank = (info.kpm_rank != null && info.kpm_total != null) ? `#${info.kpm_rank}/${info.kpm_total}` : '—';
|
||||
const hsRank = (info.hs_rank != null && info.hs_total != null) ? `#${info.hs_rank}/${info.hs_total}` : '—';
|
||||
const killCell = `${w.kills} (场均 ${w.kpm.toFixed(2)} · ${kpmRank})`;
|
||||
const hsCell = `${fmtPct(w.hsRate)} (${hsRank})`;
|
||||
const priceType = `${price}${category ? '-' + category : ''}`;
|
||||
return `
|
||||
<tr class="border-t border-gray-100 dark:border-slate-600/40">
|
||||
<td class="py-1 pr-2 font-mono">${w.weapon}</td>
|
||||
<td class="py-1 px-2 text-right font-mono">${killCell}</td>
|
||||
<td class="py-1 px-2 text-right font-mono">${hsCell}</td>
|
||||
<td class="py-1 pl-2 font-mono">${priceType}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
const roundSplit = JSON.parse({{ (features.get('rd_roundtype_split_json', '{}') or '{}') | tojson }});
|
||||
const roundSplitEl = document.getElementById('roundTypeTable');
|
||||
if (roundSplitEl) {
|
||||
const keys = Object.keys(roundSplit || {});
|
||||
if (keys.length === 0) {
|
||||
roundSplitEl.innerHTML = '<div class="text-gray-500 dark:text-gray-400">No data</div>';
|
||||
} else {
|
||||
const order = ['pistol', 'reg', 'eco', 'rifle', 'fullbuy', 'overtime'];
|
||||
keys.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
||||
const rtRank = {
|
||||
pistol: { kpr: { rank: {{ (distribution.get('rd_rt_kpr_pistol') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_kpr_pistol') or {}).get('total', 'null') }} } },
|
||||
reg: { kpr: { rank: {{ (distribution.get('rd_rt_kpr_reg') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_kpr_reg') or {}).get('total', 'null') }} } },
|
||||
overtime: { kpr: { rank: {{ (distribution.get('rd_rt_kpr_overtime') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_kpr_overtime') or {}).get('total', 'null') }} },
|
||||
perf: { rank: {{ (distribution.get('rd_rt_perf_overtime') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_perf_overtime') or {}).get('total', 'null') }} } },
|
||||
eco: { perf: { rank: {{ (distribution.get('rd_rt_perf_eco') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_perf_eco') or {}).get('total', 'null') }} } },
|
||||
rifle: { perf: { rank: {{ (distribution.get('rd_rt_perf_rifle') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_perf_rifle') or {}).get('total', 'null') }} } },
|
||||
fullbuy: { perf: { rank: {{ (distribution.get('rd_rt_perf_fullbuy') or {}).get('rank', 'null') }}, total: {{ (distribution.get('rd_rt_perf_fullbuy') or {}).get('total', 'null') }} } },
|
||||
};
|
||||
const fmtRank = (r) => (r && r.rank != null && r.total != null) ? `#${r.rank}/${r.total}` : '—';
|
||||
|
||||
roundSplitEl.innerHTML = `
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="text-gray-500 dark:text-gray-400">
|
||||
<tr>
|
||||
<th class="text-left font-bold py-1 pr-2">类型</th>
|
||||
<th class="text-right font-bold py-1 px-2">KPR</th>
|
||||
<th class="text-right font-bold py-1 px-2">队内</th>
|
||||
<th class="text-right font-bold py-1 px-2">Perf</th>
|
||||
<th class="text-right font-bold py-1 px-2">队内</th>
|
||||
<th class="text-right font-bold py-1 pl-2">样本</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-gray-700 dark:text-gray-200">
|
||||
${keys.map(k => {
|
||||
const v = roundSplit[k] || {};
|
||||
const kpr = (v.kpr != null) ? Number(v.kpr).toFixed(2) : '—';
|
||||
const perf = (v.perf != null) ? Number(v.perf).toFixed(2) : '—';
|
||||
const rounds = v.rounds != null ? v.rounds : 0;
|
||||
const rk = rtRank[k] || {};
|
||||
const kprRank = fmtRank(rk.kpr);
|
||||
const perfRank = fmtRank(rk.perf);
|
||||
return `
|
||||
<tr class="border-t border-gray-100 dark:border-slate-600/40">
|
||||
<td class="py-1 pr-2 font-mono">${k}</td>
|
||||
<td class="py-1 px-2 text-right font-mono">${kpr}</td>
|
||||
<td class="py-1 px-2 text-right font-mono">${kprRank}</td>
|
||||
<td class="py-1 px-2 text-right font-mono">${perf}</td>
|
||||
<td class="py-1 px-2 text-right font-mono">${perfRank}</td>
|
||||
<td class="py-1 pl-2 text-right font-mono">n=${rounds}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user